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
@@ -1,3 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+
5
+ logger = logging.getLogger(__name__)
6
+
1
7
  """
2
8
  Analytics API for HtmlGraph work type analysis.
3
9
 
@@ -25,19 +31,35 @@ Example:
25
31
  # Returns: 25.5 (% of work spent on maintenance)
26
32
  """
27
33
 
28
- from __future__ import annotations
29
-
30
- from datetime import datetime
34
+ from datetime import datetime, timezone
31
35
  from typing import TYPE_CHECKING
32
36
 
33
37
  if TYPE_CHECKING:
34
38
  from htmlgraph import SDK
35
39
 
36
40
  from htmlgraph.converter import html_to_session
37
- from htmlgraph.models import Session, WorkType
41
+ from htmlgraph.models import Session, WorkType, utc_now
38
42
  from htmlgraph.session_manager import SessionManager
39
43
 
40
44
 
45
+ def normalize_datetime(dt: datetime | None) -> datetime | None:
46
+ """
47
+ Normalize datetime to UTC-aware format for safe comparisons.
48
+
49
+ Handles three cases:
50
+ - None: returns None
51
+ - Naive (no timezone): assumes UTC and adds timezone
52
+ - Aware (has timezone): converts to UTC
53
+ """
54
+ if dt is None:
55
+ return None
56
+ if dt.tzinfo is None:
57
+ # Naive datetime - assume UTC
58
+ return dt.replace(tzinfo=timezone.utc)
59
+ # Already aware - convert to UTC
60
+ return dt.astimezone(timezone.utc)
61
+
62
+
41
63
  class Analytics:
42
64
  """
43
65
  Analytics interface for work type analysis.
@@ -79,7 +101,7 @@ class Analytics:
79
101
  Example:
80
102
  >>> analytics = sdk.analytics
81
103
  >>> dist = analytics.work_type_distribution(session_id="session-123")
82
- >>> print(dist)
104
+ >>> logger.info("%s", dist)
83
105
  {
84
106
  "feature-implementation": 45.2,
85
107
  "spike-investigation": 28.3,
@@ -145,11 +167,11 @@ class Analytics:
145
167
 
146
168
  Example:
147
169
  >>> ratio = sdk.analytics.spike_to_feature_ratio(session_id="session-123")
148
- >>> print(f"Spike-to-feature ratio: {ratio:.2f}")
170
+ >>> logger.info(f"Spike-to-feature ratio: {ratio:.2f}")
149
171
  Spike-to-feature ratio: 0.63
150
172
 
151
173
  >>> if ratio > 0.5:
152
- ... print("This was a research-heavy session")
174
+ ... logger.info("This was a research-heavy session")
153
175
  """
154
176
  events = self._get_events(session_id, start_date, end_date)
155
177
 
@@ -203,11 +225,11 @@ class Analytics:
203
225
 
204
226
  Example:
205
227
  >>> burden = sdk.analytics.maintenance_burden(session_id="session-123")
206
- >>> print(f"Maintenance burden: {burden:.1f}%")
228
+ >>> logger.info(f"Maintenance burden: {burden:.1f}%")
207
229
  Maintenance burden: 32.5%
208
230
 
209
231
  >>> if burden > 40:
210
- ... print("⚠️ High maintenance burden - consider addressing technical debt")
232
+ ... logger.info("⚠️ High maintenance burden - consider addressing technical debt")
211
233
  """
212
234
  events = self._get_events(session_id, start_date, end_date)
213
235
 
@@ -258,7 +280,7 @@ class Analytics:
258
280
  >>> spike_sessions = sdk.analytics.get_sessions_by_work_type(
259
281
  ... "spike-investigation"
260
282
  ... )
261
- >>> print(f"Found {len(spike_sessions)} exploratory sessions")
283
+ >>> logger.info(f"Found {len(spike_sessions)} exploratory sessions")
262
284
  """
263
285
  session_nodes = self.sdk.sessions.all()
264
286
  matching_sessions = []
@@ -270,9 +292,11 @@ class Analytics:
270
292
  continue
271
293
 
272
294
  # Check date range
273
- if start_date and session.started_at < start_date:
295
+ start_normalized = normalize_datetime(start_date)
296
+ end_normalized = normalize_datetime(end_date)
297
+ if start_normalized and session.started_at < start_normalized:
274
298
  continue
275
- if end_date and session.started_at > end_date:
299
+ if end_normalized and session.started_at > end_normalized:
276
300
  continue
277
301
 
278
302
  # Check primary work type
@@ -296,7 +320,7 @@ class Analytics:
296
320
 
297
321
  Example:
298
322
  >>> breakdown = sdk.analytics.calculate_session_work_breakdown("session-123")
299
- >>> print(breakdown)
323
+ >>> logger.info("%s", breakdown)
300
324
  {
301
325
  "feature-implementation": 45,
302
326
  "spike-investigation": 28,
@@ -324,7 +348,7 @@ class Analytics:
324
348
 
325
349
  Example:
326
350
  >>> primary = sdk.analytics.calculate_session_primary_work_type("session-123")
327
- >>> print(f"Primary work type: {primary}")
351
+ >>> logger.info(f"Primary work type: {primary}")
328
352
  Primary work type: spike-investigation
329
353
  """
330
354
  session = self._get_session(session_id)
@@ -379,7 +403,7 @@ class Analytics:
379
403
 
380
404
  Example:
381
405
  >>> metrics = sdk.analytics.transition_time_metrics(session_id="session-123")
382
- >>> print(f"Transition time: {metrics['transition_percent']:.1f}%")
406
+ >>> logger.info(f"Transition time: {metrics['transition_percent']:.1f}%")
383
407
  Transition time: 15.3%
384
408
  """
385
409
  from pathlib import Path
@@ -413,18 +437,26 @@ class Analytics:
413
437
  # Calculate time for each spike
414
438
  for spike in all_spikes:
415
439
  # Apply date filters
416
- if start_date and spike.created < start_date:
440
+ start_normalized = normalize_datetime(start_date)
441
+ end_normalized = normalize_datetime(end_date)
442
+ if start_normalized and spike.created < start_normalized:
417
443
  continue
418
- if end_date and spike.created > end_date:
444
+ if end_normalized and spike.created > end_normalized:
419
445
  continue
420
446
 
421
- # Calculate duration
422
- start_time = spike.created
447
+ # Calculate duration (normalize datetimes for safe comparison)
448
+ start_time = normalize_datetime(spike.created)
449
+ if not start_time:
450
+ continue # Skip if spike creation date is missing
423
451
  if spike.status == "done" and spike.updated:
424
- end_time = spike.updated
452
+ end_time = normalize_datetime(spike.updated)
425
453
  else:
426
454
  # If still in progress, use last updated time
427
- end_time = spike.updated if spike.updated else datetime.now()
455
+ end_time = normalize_datetime(
456
+ spike.updated if spike.updated else utc_now()
457
+ )
458
+ if not end_time:
459
+ end_time = start_time # Fallback to start time if end time missing
428
460
 
429
461
  duration = (
430
462
  end_time - start_time
@@ -460,17 +492,25 @@ class Analytics:
460
492
 
461
493
  for node in nodes:
462
494
  # Apply date filters
463
- if start_date and node.created < start_date:
495
+ start_normalized = normalize_datetime(start_date)
496
+ end_normalized = normalize_datetime(end_date)
497
+ if start_normalized and node.created < start_normalized:
464
498
  continue
465
- if end_date and node.created > end_date:
499
+ if end_normalized and node.created > end_normalized:
466
500
  continue
467
501
 
468
- # Calculate duration
469
- start_time = node.created
502
+ # Calculate duration (normalize datetimes for safe comparison)
503
+ start_time = normalize_datetime(node.created)
504
+ if not start_time:
505
+ continue # Skip if node creation date is missing
470
506
  if node.status == "done" and node.updated:
471
- end_time = node.updated
507
+ end_time = normalize_datetime(node.updated)
472
508
  else:
473
- end_time = node.updated if node.updated else datetime.now()
509
+ end_time = normalize_datetime(
510
+ node.updated if node.updated else utc_now()
511
+ )
512
+ if not end_time:
513
+ end_time = start_time # Fallback to start time if end time missing
474
514
 
475
515
  duration = (end_time - start_time).total_seconds() / 60
476
516
  feature_minutes += duration
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  SQLite analytics index for HtmlGraph event logs.
3
5
 
@@ -5,7 +7,6 @@ This is a rebuildable cache/index for fast dashboard queries.
5
7
  The canonical source of truth is the JSONL event log under `.htmlgraph/events/`.
6
8
  """
7
9
 
8
- from __future__ import annotations
9
10
 
10
11
  import json
11
12
  import sqlite3
@@ -14,7 +15,7 @@ from dataclasses import dataclass
14
15
  from pathlib import Path
15
16
  from typing import Any
16
17
 
17
- SCHEMA_VERSION = 2
18
+ SCHEMA_VERSION = 4 # Bumped: renamed 'agent' column to 'agent_assigned'
18
19
 
19
20
 
20
21
  @dataclass(frozen=True)
@@ -82,12 +83,14 @@ class AnalyticsIndex:
82
83
  """
83
84
  CREATE TABLE IF NOT EXISTS sessions (
84
85
  session_id TEXT PRIMARY KEY,
85
- agent TEXT,
86
+ agent_assigned TEXT,
86
87
  start_commit TEXT,
87
88
  continued_from TEXT,
88
89
  status TEXT,
89
90
  started_at TEXT,
90
- ended_at TEXT
91
+ ended_at TEXT,
92
+ parent_session_id TEXT,
93
+ parent_event_id TEXT
91
94
  )
92
95
  """
93
96
  )
@@ -103,6 +106,9 @@ class AnalyticsIndex:
103
106
  feature_id TEXT,
104
107
  drift_score REAL,
105
108
  payload_json TEXT,
109
+ parent_event_id TEXT,
110
+ cost_tokens INTEGER,
111
+ execution_duration_seconds REAL,
106
112
  FOREIGN KEY(session_id) REFERENCES sessions(session_id)
107
113
  )
108
114
  """
@@ -157,6 +163,9 @@ class AnalyticsIndex:
157
163
  )
158
164
 
159
165
  # Indexes for typical dashboard queries
166
+ conn.execute(
167
+ "CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id)"
168
+ )
160
169
  conn.execute("CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts)")
161
170
  conn.execute(
162
171
  "CREATE INDEX IF NOT EXISTS idx_events_session_ts ON events(session_id, ts)"
@@ -190,15 +199,17 @@ class AnalyticsIndex:
190
199
  with self.connect() as conn:
191
200
  conn.execute(
192
201
  """
193
- INSERT INTO sessions(session_id, agent, start_commit, continued_from, status, started_at, ended_at)
194
- VALUES(?,?,?,?,?,?,?)
202
+ INSERT INTO sessions(session_id, agent_assigned, start_commit, continued_from, status, started_at, ended_at, parent_session_id, parent_event_id)
203
+ VALUES(?,?,?,?,?,?,?,?,?)
195
204
  ON CONFLICT(session_id) DO UPDATE SET
196
- agent=excluded.agent,
205
+ agent_assigned=excluded.agent_assigned,
197
206
  start_commit=excluded.start_commit,
198
207
  continued_from=excluded.continued_from,
199
208
  status=excluded.status,
200
209
  started_at=excluded.started_at,
201
- ended_at=excluded.ended_at
210
+ ended_at=excluded.ended_at,
211
+ parent_session_id=excluded.parent_session_id,
212
+ parent_event_id=excluded.parent_event_id
202
213
  """,
203
214
  (
204
215
  session.get("session_id"),
@@ -208,6 +219,8 @@ class AnalyticsIndex:
208
219
  session.get("status"),
209
220
  session.get("started_at"),
210
221
  session.get("ended_at"),
222
+ session.get("parent_session_id"),
223
+ session.get("parent_event_id"),
211
224
  ),
212
225
  )
213
226
 
@@ -238,8 +251,8 @@ class AnalyticsIndex:
238
251
  with self.connect() as conn:
239
252
  conn.execute(
240
253
  """
241
- INSERT OR IGNORE INTO events(event_id, session_id, ts, tool, summary, success, feature_id, drift_score, payload_json)
242
- VALUES(?,?,?,?,?,?,?,?,?)
254
+ INSERT OR IGNORE INTO events(event_id, session_id, ts, tool, summary, success, feature_id, drift_score, payload_json, parent_event_id, cost_tokens, execution_duration_seconds)
255
+ VALUES(?,?,?,?,?,?,?,?,?,?,?,?)
243
256
  """,
244
257
  (
245
258
  event_id,
@@ -251,6 +264,9 @@ class AnalyticsIndex:
251
264
  event.get("feature_id"),
252
265
  event.get("drift_score"),
253
266
  payload_json,
267
+ event.get("parent_event_id"),
268
+ event.get("cost_tokens"),
269
+ event.get("execution_duration_seconds"),
254
270
  ),
255
271
  )
256
272
  # Insert file path rows, idempotent by (event_id, path)
@@ -367,6 +383,8 @@ class AnalyticsIndex:
367
383
  "status": event.get("session_status"),
368
384
  "started_at": None,
369
385
  "ended_at": None,
386
+ "parent_session_id": event.get("parent_session_id"),
387
+ "parent_event_id": event.get("parent_event_id"),
370
388
  },
371
389
  )
372
390
  if meta.get("agent") is None and event.get("agent"):
@@ -377,6 +395,12 @@ class AnalyticsIndex:
377
395
  meta["continued_from"] = event.get("continued_from")
378
396
  if meta.get("status") is None and event.get("session_status"):
379
397
  meta["status"] = event.get("session_status")
398
+ if meta.get("parent_session_id") is None and event.get(
399
+ "parent_session_id"
400
+ ):
401
+ meta["parent_session_id"] = event.get("parent_session_id")
402
+ if meta.get("parent_event_id") is None and event.get("parent_event_id"):
403
+ meta["parent_event_id"] = event.get("parent_event_id")
380
404
 
381
405
  # Track time range (treat earliest event as started_at, latest as ended_at if session is ended)
382
406
  if meta["started_at"] is None or ts < meta["started_at"]:
@@ -393,8 +417,8 @@ class AnalyticsIndex:
393
417
 
394
418
  conn.execute(
395
419
  """
396
- INSERT OR IGNORE INTO events(event_id, session_id, ts, tool, summary, success, feature_id, drift_score, payload_json)
397
- VALUES(?,?,?,?,?,?,?,?,?)
420
+ INSERT OR IGNORE INTO events(event_id, session_id, ts, tool, summary, success, feature_id, drift_score, payload_json, parent_event_id, cost_tokens, execution_duration_seconds)
421
+ VALUES(?,?,?,?,?,?,?,?,?,?,?,?)
398
422
  """,
399
423
  (
400
424
  event_id,
@@ -406,6 +430,9 @@ class AnalyticsIndex:
406
430
  event.get("feature_id"),
407
431
  event.get("drift_score"),
408
432
  payload_json,
433
+ event.get("parent_event_id"),
434
+ event.get("cost_tokens"),
435
+ event.get("execution_duration_seconds"),
409
436
  ),
410
437
  )
411
438
 
@@ -483,17 +510,19 @@ class AnalyticsIndex:
483
510
  for meta in session_meta.values():
484
511
  conn.execute(
485
512
  """
486
- INSERT INTO sessions(session_id, agent, start_commit, continued_from, status, started_at, ended_at)
487
- VALUES(?,?,?,?,?,?,?)
513
+ INSERT INTO sessions(session_id, agent_assigned, start_commit, continued_from, status, started_at, ended_at, parent_session_id, parent_event_id)
514
+ VALUES(?,?,?,?,?,?,?,?,?)
488
515
  """,
489
516
  (
490
517
  meta.get("session_id"),
491
- meta.get("agent"),
518
+ meta.get("agent"), # Source data still uses 'agent' key
492
519
  meta.get("start_commit"),
493
520
  meta.get("continued_from"),
494
521
  meta.get("status"),
495
522
  meta.get("started_at"),
496
523
  meta.get("ended_at"),
524
+ meta.get("parent_session_id"),
525
+ meta.get("parent_event_id"),
497
526
  ),
498
527
  )
499
528
 
@@ -676,13 +705,17 @@ class AnalyticsIndex:
676
705
  with self.connect() as conn:
677
706
  rows = conn.execute(
678
707
  """
679
- SELECT event_id, session_id, ts, tool, summary, success, feature_id, drift_score
680
- FROM events
681
- WHERE session_id=?
682
- ORDER BY ts DESC
708
+ SELECT e.event_id, e.session_id, e.ts, e.tool, e.summary, e.success, e.feature_id, e.drift_score,
709
+ COALESCE(e.parent_event_id, s.parent_event_id) as parent_event_id,
710
+ e.cost_tokens, e.execution_duration_seconds
711
+ FROM events e
712
+ JOIN sessions s ON e.session_id = s.session_id
713
+ WHERE e.session_id = ?
714
+ OR s.parent_session_id = ?
715
+ ORDER BY e.ts DESC
683
716
  LIMIT ?
684
717
  """,
685
- (session_id, int(limit)),
718
+ (session_id, session_id, int(limit)),
686
719
  ).fetchall()
687
720
  return [dict(r) for r in rows]
688
721
 
@@ -0,0 +1,3 @@
1
+ """HtmlGraph FastAPI Backend - Real-time Agent Observability Dashboard."""
2
+
3
+ __all__ = ["app", "get_app"]