htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
  2. htmlgraph/.htmlgraph/agents.json +72 -0
  3. htmlgraph/.htmlgraph/htmlgraph.db +0 -0
  4. htmlgraph/__init__.py +51 -1
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +26 -10
  7. htmlgraph/agent_registry.py +2 -1
  8. htmlgraph/analytics/__init__.py +8 -1
  9. htmlgraph/analytics/cli.py +86 -20
  10. htmlgraph/analytics/cost_analyzer.py +391 -0
  11. htmlgraph/analytics/cost_monitor.py +664 -0
  12. htmlgraph/analytics/cost_reporter.py +675 -0
  13. htmlgraph/analytics/cross_session.py +617 -0
  14. htmlgraph/analytics/dependency.py +10 -6
  15. htmlgraph/analytics/pattern_learning.py +771 -0
  16. htmlgraph/analytics/session_graph.py +707 -0
  17. htmlgraph/analytics/strategic/__init__.py +80 -0
  18. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  19. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  20. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  21. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  22. htmlgraph/analytics/work_type.py +67 -27
  23. htmlgraph/analytics_index.py +53 -20
  24. htmlgraph/api/__init__.py +3 -0
  25. htmlgraph/api/cost_alerts_websocket.py +416 -0
  26. htmlgraph/api/main.py +2498 -0
  27. htmlgraph/api/static/htmx.min.js +1 -0
  28. htmlgraph/api/static/style-redesign.css +1344 -0
  29. htmlgraph/api/static/style.css +1079 -0
  30. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  31. htmlgraph/api/templates/dashboard.html +794 -0
  32. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  33. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  34. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  35. htmlgraph/api/templates/partials/agents.html +317 -0
  36. htmlgraph/api/templates/partials/event-traces.html +373 -0
  37. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  38. htmlgraph/api/templates/partials/features.html +578 -0
  39. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  40. htmlgraph/api/templates/partials/metrics.html +346 -0
  41. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  42. htmlgraph/api/templates/partials/orchestration.html +198 -0
  43. htmlgraph/api/templates/partials/spawners.html +375 -0
  44. htmlgraph/api/templates/partials/work-items.html +613 -0
  45. htmlgraph/api/websocket.py +538 -0
  46. htmlgraph/archive/__init__.py +24 -0
  47. htmlgraph/archive/bloom.py +234 -0
  48. htmlgraph/archive/fts.py +297 -0
  49. htmlgraph/archive/manager.py +583 -0
  50. htmlgraph/archive/search.py +244 -0
  51. htmlgraph/atomic_ops.py +560 -0
  52. htmlgraph/attribute_index.py +2 -1
  53. htmlgraph/bounded_paths.py +539 -0
  54. htmlgraph/builders/base.py +57 -2
  55. htmlgraph/builders/bug.py +19 -3
  56. htmlgraph/builders/chore.py +19 -3
  57. htmlgraph/builders/epic.py +19 -3
  58. htmlgraph/builders/feature.py +27 -3
  59. htmlgraph/builders/insight.py +2 -1
  60. htmlgraph/builders/metric.py +2 -1
  61. htmlgraph/builders/pattern.py +2 -1
  62. htmlgraph/builders/phase.py +19 -3
  63. htmlgraph/builders/spike.py +29 -3
  64. htmlgraph/builders/track.py +42 -1
  65. htmlgraph/cigs/__init__.py +81 -0
  66. htmlgraph/cigs/autonomy.py +385 -0
  67. htmlgraph/cigs/cost.py +475 -0
  68. htmlgraph/cigs/messages_basic.py +472 -0
  69. htmlgraph/cigs/messaging.py +365 -0
  70. htmlgraph/cigs/models.py +771 -0
  71. htmlgraph/cigs/pattern_storage.py +427 -0
  72. htmlgraph/cigs/patterns.py +503 -0
  73. htmlgraph/cigs/posttool_analyzer.py +234 -0
  74. htmlgraph/cigs/reporter.py +818 -0
  75. htmlgraph/cigs/tracker.py +317 -0
  76. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  77. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  78. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  79. htmlgraph/cli/__init__.py +42 -0
  80. htmlgraph/cli/__main__.py +6 -0
  81. htmlgraph/cli/analytics.py +1424 -0
  82. htmlgraph/cli/base.py +685 -0
  83. htmlgraph/cli/constants.py +206 -0
  84. htmlgraph/cli/core.py +954 -0
  85. htmlgraph/cli/main.py +147 -0
  86. htmlgraph/cli/models.py +475 -0
  87. htmlgraph/cli/templates/__init__.py +1 -0
  88. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  89. htmlgraph/cli/work/__init__.py +239 -0
  90. htmlgraph/cli/work/browse.py +115 -0
  91. htmlgraph/cli/work/features.py +568 -0
  92. htmlgraph/cli/work/orchestration.py +676 -0
  93. htmlgraph/cli/work/report.py +728 -0
  94. htmlgraph/cli/work/sessions.py +466 -0
  95. htmlgraph/cli/work/snapshot.py +559 -0
  96. htmlgraph/cli/work/tracks.py +486 -0
  97. htmlgraph/cli_commands/__init__.py +1 -0
  98. htmlgraph/cli_commands/feature.py +195 -0
  99. htmlgraph/cli_framework.py +115 -0
  100. htmlgraph/collections/__init__.py +2 -0
  101. htmlgraph/collections/base.py +197 -14
  102. htmlgraph/collections/bug.py +2 -1
  103. htmlgraph/collections/chore.py +2 -1
  104. htmlgraph/collections/epic.py +2 -1
  105. htmlgraph/collections/feature.py +2 -1
  106. htmlgraph/collections/insight.py +2 -1
  107. htmlgraph/collections/metric.py +2 -1
  108. htmlgraph/collections/pattern.py +2 -1
  109. htmlgraph/collections/phase.py +2 -1
  110. htmlgraph/collections/session.py +194 -0
  111. htmlgraph/collections/spike.py +13 -2
  112. htmlgraph/collections/task_delegation.py +241 -0
  113. htmlgraph/collections/todo.py +14 -1
  114. htmlgraph/collections/traces.py +487 -0
  115. htmlgraph/config/cost_models.json +56 -0
  116. htmlgraph/config.py +190 -0
  117. htmlgraph/context_analytics.py +2 -1
  118. htmlgraph/converter.py +116 -7
  119. htmlgraph/cost_analysis/__init__.py +5 -0
  120. htmlgraph/cost_analysis/analyzer.py +438 -0
  121. htmlgraph/dashboard.html +2246 -248
  122. htmlgraph/dashboard.html.backup +6592 -0
  123. htmlgraph/dashboard.html.bak +7181 -0
  124. htmlgraph/dashboard.html.bak2 +7231 -0
  125. htmlgraph/dashboard.html.bak3 +7232 -0
  126. htmlgraph/db/__init__.py +38 -0
  127. htmlgraph/db/queries.py +790 -0
  128. htmlgraph/db/schema.py +1788 -0
  129. htmlgraph/decorators.py +317 -0
  130. htmlgraph/dependency_models.py +2 -1
  131. htmlgraph/deploy.py +26 -27
  132. htmlgraph/docs/API_REFERENCE.md +841 -0
  133. htmlgraph/docs/HTTP_API.md +750 -0
  134. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  135. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  136. htmlgraph/docs/README.md +532 -0
  137. htmlgraph/docs/__init__.py +77 -0
  138. htmlgraph/docs/docs_version.py +55 -0
  139. htmlgraph/docs/metadata.py +93 -0
  140. htmlgraph/docs/migrations.py +232 -0
  141. htmlgraph/docs/template_engine.py +143 -0
  142. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  143. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  144. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  145. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  146. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  147. htmlgraph/docs/version_check.py +163 -0
  148. htmlgraph/edge_index.py +2 -1
  149. htmlgraph/error_handler.py +544 -0
  150. htmlgraph/event_log.py +86 -37
  151. htmlgraph/event_migration.py +2 -1
  152. htmlgraph/file_watcher.py +12 -8
  153. htmlgraph/find_api.py +2 -1
  154. htmlgraph/git_events.py +67 -9
  155. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  156. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  157. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  158. htmlgraph/hooks/__init__.py +8 -0
  159. htmlgraph/hooks/bootstrap.py +169 -0
  160. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  161. htmlgraph/hooks/concurrent_sessions.py +208 -0
  162. htmlgraph/hooks/context.py +350 -0
  163. htmlgraph/hooks/drift_handler.py +525 -0
  164. htmlgraph/hooks/event_tracker.py +790 -99
  165. htmlgraph/hooks/git_commands.py +175 -0
  166. htmlgraph/hooks/installer.py +5 -1
  167. htmlgraph/hooks/orchestrator.py +327 -76
  168. htmlgraph/hooks/orchestrator_reflector.py +31 -4
  169. htmlgraph/hooks/post_tool_use_failure.py +32 -7
  170. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  171. htmlgraph/hooks/posttooluse.py +92 -19
  172. htmlgraph/hooks/pretooluse.py +527 -7
  173. htmlgraph/hooks/prompt_analyzer.py +637 -0
  174. htmlgraph/hooks/session_handler.py +668 -0
  175. htmlgraph/hooks/session_summary.py +395 -0
  176. htmlgraph/hooks/state_manager.py +504 -0
  177. htmlgraph/hooks/subagent_detection.py +202 -0
  178. htmlgraph/hooks/subagent_stop.py +369 -0
  179. htmlgraph/hooks/task_enforcer.py +99 -4
  180. htmlgraph/hooks/validator.py +212 -91
  181. htmlgraph/ids.py +2 -1
  182. htmlgraph/learning.py +125 -100
  183. htmlgraph/mcp_server.py +2 -1
  184. htmlgraph/models.py +217 -18
  185. htmlgraph/operations/README.md +62 -0
  186. htmlgraph/operations/__init__.py +79 -0
  187. htmlgraph/operations/analytics.py +339 -0
  188. htmlgraph/operations/bootstrap.py +289 -0
  189. htmlgraph/operations/events.py +244 -0
  190. htmlgraph/operations/fastapi_server.py +231 -0
  191. htmlgraph/operations/hooks.py +350 -0
  192. htmlgraph/operations/initialization.py +597 -0
  193. htmlgraph/operations/initialization.py.backup +228 -0
  194. htmlgraph/operations/server.py +303 -0
  195. htmlgraph/orchestration/__init__.py +58 -0
  196. htmlgraph/orchestration/claude_launcher.py +179 -0
  197. htmlgraph/orchestration/command_builder.py +72 -0
  198. htmlgraph/orchestration/headless_spawner.py +281 -0
  199. htmlgraph/orchestration/live_events.py +377 -0
  200. htmlgraph/orchestration/model_selection.py +327 -0
  201. htmlgraph/orchestration/plugin_manager.py +140 -0
  202. htmlgraph/orchestration/prompts.py +137 -0
  203. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  204. htmlgraph/orchestration/spawners/__init__.py +16 -0
  205. htmlgraph/orchestration/spawners/base.py +194 -0
  206. htmlgraph/orchestration/spawners/claude.py +173 -0
  207. htmlgraph/orchestration/spawners/codex.py +435 -0
  208. htmlgraph/orchestration/spawners/copilot.py +294 -0
  209. htmlgraph/orchestration/spawners/gemini.py +471 -0
  210. htmlgraph/orchestration/subprocess_runner.py +36 -0
  211. htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
  212. htmlgraph/orchestration.md +563 -0
  213. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  214. htmlgraph/orchestrator.py +2 -1
  215. htmlgraph/orchestrator_config.py +357 -0
  216. htmlgraph/orchestrator_mode.py +115 -4
  217. htmlgraph/parallel.py +2 -1
  218. htmlgraph/parser.py +86 -6
  219. htmlgraph/path_query.py +608 -0
  220. htmlgraph/pattern_matcher.py +636 -0
  221. htmlgraph/pydantic_models.py +476 -0
  222. htmlgraph/quality_gates.py +350 -0
  223. htmlgraph/query_builder.py +2 -1
  224. htmlgraph/query_composer.py +509 -0
  225. htmlgraph/reflection.py +443 -0
  226. htmlgraph/refs.py +344 -0
  227. htmlgraph/repo_hash.py +512 -0
  228. htmlgraph/repositories/__init__.py +292 -0
  229. htmlgraph/repositories/analytics_repository.py +455 -0
  230. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  231. htmlgraph/repositories/feature_repository.py +581 -0
  232. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  233. htmlgraph/repositories/feature_repository_memory.py +607 -0
  234. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  235. htmlgraph/repositories/filter_service.py +620 -0
  236. htmlgraph/repositories/filter_service_standard.py +445 -0
  237. htmlgraph/repositories/shared_cache.py +621 -0
  238. htmlgraph/repositories/shared_cache_memory.py +395 -0
  239. htmlgraph/repositories/track_repository.py +552 -0
  240. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  241. htmlgraph/repositories/track_repository_memory.py +508 -0
  242. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  243. htmlgraph/sdk/__init__.py +398 -0
  244. htmlgraph/sdk/__init__.pyi +14 -0
  245. htmlgraph/sdk/analytics/__init__.py +19 -0
  246. htmlgraph/sdk/analytics/engine.py +155 -0
  247. htmlgraph/sdk/analytics/helpers.py +178 -0
  248. htmlgraph/sdk/analytics/registry.py +109 -0
  249. htmlgraph/sdk/base.py +484 -0
  250. htmlgraph/sdk/constants.py +216 -0
  251. htmlgraph/sdk/core.pyi +308 -0
  252. htmlgraph/sdk/discovery.py +120 -0
  253. htmlgraph/sdk/help/__init__.py +12 -0
  254. htmlgraph/sdk/help/mixin.py +699 -0
  255. htmlgraph/sdk/mixins/__init__.py +15 -0
  256. htmlgraph/sdk/mixins/attribution.py +113 -0
  257. htmlgraph/sdk/mixins/mixin.py +410 -0
  258. htmlgraph/sdk/operations/__init__.py +12 -0
  259. htmlgraph/sdk/operations/mixin.py +427 -0
  260. htmlgraph/sdk/orchestration/__init__.py +17 -0
  261. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  262. htmlgraph/sdk/orchestration/spawner.py +204 -0
  263. htmlgraph/sdk/planning/__init__.py +19 -0
  264. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  265. htmlgraph/sdk/planning/mixin.py +211 -0
  266. htmlgraph/sdk/planning/parallel.py +186 -0
  267. htmlgraph/sdk/planning/queue.py +210 -0
  268. htmlgraph/sdk/planning/recommendations.py +87 -0
  269. htmlgraph/sdk/planning/smart_planning.py +319 -0
  270. htmlgraph/sdk/session/__init__.py +19 -0
  271. htmlgraph/sdk/session/continuity.py +57 -0
  272. htmlgraph/sdk/session/handoff.py +110 -0
  273. htmlgraph/sdk/session/info.py +309 -0
  274. htmlgraph/sdk/session/manager.py +103 -0
  275. htmlgraph/sdk/strategic/__init__.py +26 -0
  276. htmlgraph/sdk/strategic/mixin.py +563 -0
  277. htmlgraph/server.py +295 -107
  278. htmlgraph/session_hooks.py +300 -0
  279. htmlgraph/session_manager.py +285 -3
  280. htmlgraph/session_registry.py +587 -0
  281. htmlgraph/session_state.py +436 -0
  282. htmlgraph/session_warning.py +2 -1
  283. htmlgraph/sessions/__init__.py +23 -0
  284. htmlgraph/sessions/handoff.py +756 -0
  285. htmlgraph/system_prompts.py +450 -0
  286. htmlgraph/templates/orchestration-view.html +350 -0
  287. htmlgraph/track_builder.py +33 -1
  288. htmlgraph/track_manager.py +38 -0
  289. htmlgraph/transcript.py +18 -5
  290. htmlgraph/validation.py +115 -0
  291. htmlgraph/watch.py +2 -1
  292. htmlgraph/work_type_utils.py +2 -1
  293. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
  294. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
  295. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  296. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  297. htmlgraph/cli.py +0 -4839
  298. htmlgraph/sdk.py +0 -2359
  299. htmlgraph-0.20.1.dist-info/RECORD +0 -118
  300. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  301. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  302. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  303. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  304. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,339 @@
1
+ from __future__ import annotations
2
+
3
+ """Analytics operations for HtmlGraph."""
4
+
5
+
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from htmlgraph import SDK
11
+ from htmlgraph.converter import html_to_session
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class AnalyticsSessionResult:
16
+ """Result of analyzing a single session."""
17
+
18
+ session_id: str
19
+ metrics: dict[str, Any]
20
+ warnings: list[str]
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class AnalyticsProjectResult:
25
+ """Result of analyzing project-wide analytics."""
26
+
27
+ metrics: dict[str, Any]
28
+ warnings: list[str]
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class RecommendationsResult:
33
+ """Result of getting work recommendations."""
34
+
35
+ recommendations: list[dict[str, Any]]
36
+ reasoning: dict[str, Any]
37
+ warnings: list[str]
38
+
39
+
40
+ class AnalyticsOperationError(RuntimeError):
41
+ """Base error for analytics operations."""
42
+
43
+
44
+ def analyze_session(*, graph_dir: Path, session_id: str) -> AnalyticsSessionResult:
45
+ """
46
+ Compute analytics for a single session.
47
+
48
+ Args:
49
+ graph_dir: Path to .htmlgraph directory
50
+ session_id: ID of the session to analyze
51
+
52
+ Returns:
53
+ AnalyticsSessionResult with session metrics and warnings
54
+
55
+ Raises:
56
+ AnalyticsOperationError: If session cannot be analyzed
57
+ """
58
+ warnings: list[str] = []
59
+
60
+ # Validate inputs
61
+ if not graph_dir.exists():
62
+ raise AnalyticsOperationError(f"Graph directory does not exist: {graph_dir}")
63
+
64
+ session_path = graph_dir / "sessions" / f"{session_id}.html"
65
+ if not session_path.exists():
66
+ raise AnalyticsOperationError(f"Session not found: {session_id}")
67
+
68
+ try:
69
+ # Load session
70
+ session = html_to_session(session_path)
71
+ except Exception as e:
72
+ raise AnalyticsOperationError(f"Failed to load session {session_id}: {e}")
73
+
74
+ try:
75
+ # Initialize SDK with minimal agent
76
+ sdk = SDK(directory=str(graph_dir), agent="analytics-ops")
77
+
78
+ # Compute metrics
79
+ metrics: dict[str, Any] = {}
80
+
81
+ # Work distribution
82
+ try:
83
+ work_dist = sdk.analytics.work_type_distribution(session_id=session_id)
84
+ metrics["work_distribution"] = work_dist
85
+ except Exception as e:
86
+ warnings.append(f"Failed to compute work distribution: {e}")
87
+ metrics["work_distribution"] = {}
88
+
89
+ # Spike-to-feature ratio
90
+ try:
91
+ spike_ratio = sdk.analytics.spike_to_feature_ratio(session_id=session_id)
92
+ metrics["spike_to_feature_ratio"] = spike_ratio
93
+ except Exception as e:
94
+ warnings.append(f"Failed to compute spike ratio: {e}")
95
+ metrics["spike_to_feature_ratio"] = 0.0
96
+
97
+ # Maintenance burden
98
+ try:
99
+ maintenance = sdk.analytics.maintenance_burden(session_id=session_id)
100
+ metrics["maintenance_burden"] = maintenance
101
+ except Exception as e:
102
+ warnings.append(f"Failed to compute maintenance burden: {e}")
103
+ metrics["maintenance_burden"] = 0.0
104
+
105
+ # Primary work type
106
+ try:
107
+ primary = sdk.analytics.calculate_session_primary_work_type(session_id)
108
+ metrics["primary_work_type"] = primary
109
+ except Exception as e:
110
+ warnings.append(f"Failed to compute primary work type: {e}")
111
+ metrics["primary_work_type"] = None
112
+
113
+ # Work breakdown (event counts)
114
+ try:
115
+ breakdown = sdk.analytics.calculate_session_work_breakdown(session_id)
116
+ metrics["work_breakdown"] = breakdown
117
+ metrics["total_events"] = sum(breakdown.values()) if breakdown else 0
118
+ except Exception as e:
119
+ warnings.append(f"Failed to compute work breakdown: {e}")
120
+ metrics["work_breakdown"] = {}
121
+ metrics["total_events"] = session.event_count
122
+
123
+ # Transition time metrics
124
+ try:
125
+ transition = sdk.analytics.transition_time_metrics(session_id=session_id)
126
+ metrics["transition_metrics"] = transition
127
+ except Exception as e:
128
+ warnings.append(f"Failed to compute transition metrics: {e}")
129
+ metrics["transition_metrics"] = {}
130
+
131
+ # Session metadata
132
+ metrics["session_id"] = session.id
133
+ metrics["agent"] = session.agent
134
+ metrics["status"] = session.status
135
+ metrics["started_at"] = session.started_at.isoformat()
136
+ if session.ended_at:
137
+ metrics["ended_at"] = session.ended_at.isoformat()
138
+
139
+ return AnalyticsSessionResult(
140
+ session_id=session_id, metrics=metrics, warnings=warnings
141
+ )
142
+
143
+ except AnalyticsOperationError:
144
+ raise
145
+ except Exception as e:
146
+ raise AnalyticsOperationError(f"Failed to analyze session {session_id}: {e}")
147
+
148
+
149
+ def analyze_project(*, graph_dir: Path) -> AnalyticsProjectResult:
150
+ """
151
+ Compute analytics for the project.
152
+
153
+ Args:
154
+ graph_dir: Path to .htmlgraph directory
155
+
156
+ Returns:
157
+ AnalyticsProjectResult with project metrics and warnings
158
+
159
+ Raises:
160
+ AnalyticsOperationError: If project cannot be analyzed
161
+ """
162
+ warnings: list[str] = []
163
+
164
+ # Validate inputs
165
+ if not graph_dir.exists():
166
+ raise AnalyticsOperationError(f"Graph directory does not exist: {graph_dir}")
167
+
168
+ sessions_dir = graph_dir / "sessions"
169
+ if not sessions_dir.exists():
170
+ warnings.append("No sessions directory found")
171
+ return AnalyticsProjectResult(metrics={"total_sessions": 0}, warnings=warnings)
172
+
173
+ try:
174
+ # Initialize SDK
175
+ sdk = SDK(directory=str(graph_dir), agent="analytics-ops")
176
+
177
+ # Get session count
178
+ session_files = sorted(
179
+ sessions_dir.glob("*.html"), key=lambda p: p.stat().st_mtime, reverse=True
180
+ )
181
+ total_sessions = len(session_files)
182
+
183
+ # Compute metrics
184
+ metrics: dict[str, Any] = {
185
+ "total_sessions": total_sessions,
186
+ }
187
+
188
+ if total_sessions == 0:
189
+ warnings.append("No sessions found in project")
190
+ return AnalyticsProjectResult(metrics=metrics, warnings=warnings)
191
+
192
+ # Project-wide work distribution
193
+ try:
194
+ work_dist = sdk.analytics.work_type_distribution()
195
+ metrics["work_distribution"] = work_dist
196
+ except Exception as e:
197
+ warnings.append(f"Failed to compute work distribution: {e}")
198
+ metrics["work_distribution"] = {}
199
+
200
+ # Project-wide spike-to-feature ratio
201
+ try:
202
+ spike_ratio = sdk.analytics.spike_to_feature_ratio()
203
+ metrics["spike_to_feature_ratio"] = spike_ratio
204
+ except Exception as e:
205
+ warnings.append(f"Failed to compute spike ratio: {e}")
206
+ metrics["spike_to_feature_ratio"] = 0.0
207
+
208
+ # Project-wide maintenance burden
209
+ try:
210
+ maintenance = sdk.analytics.maintenance_burden()
211
+ metrics["maintenance_burden"] = maintenance
212
+ except Exception as e:
213
+ warnings.append(f"Failed to compute maintenance burden: {e}")
214
+ metrics["maintenance_burden"] = 0.0
215
+
216
+ # Project-wide transition metrics
217
+ try:
218
+ transition = sdk.analytics.transition_time_metrics()
219
+ metrics["transition_metrics"] = transition
220
+ except Exception as e:
221
+ warnings.append(f"Failed to compute transition metrics: {e}")
222
+ metrics["transition_metrics"] = {}
223
+
224
+ # Session type breakdown
225
+ try:
226
+ from htmlgraph import WorkType
227
+
228
+ spike_sessions = sdk.analytics.get_sessions_by_work_type(
229
+ WorkType.SPIKE.value
230
+ )
231
+ feature_sessions = sdk.analytics.get_sessions_by_work_type(
232
+ WorkType.FEATURE.value
233
+ )
234
+ maintenance_sessions = sdk.analytics.get_sessions_by_work_type(
235
+ WorkType.MAINTENANCE.value
236
+ )
237
+
238
+ metrics["session_types"] = {
239
+ "spike": len(spike_sessions),
240
+ "feature": len(feature_sessions),
241
+ "maintenance": len(maintenance_sessions),
242
+ }
243
+ except Exception as e:
244
+ warnings.append(f"Failed to compute session types: {e}")
245
+ metrics["session_types"] = {}
246
+
247
+ # Recent sessions (metadata only)
248
+ try:
249
+ recent_sessions = []
250
+ for session_path in session_files[:5]: # Top 5 most recent
251
+ try:
252
+ session = html_to_session(session_path)
253
+ primary = (
254
+ sdk.analytics.calculate_session_primary_work_type(session.id)
255
+ or "unknown"
256
+ )
257
+ recent_sessions.append(
258
+ {
259
+ "session_id": session.id,
260
+ "agent": session.agent,
261
+ "started_at": session.started_at.isoformat(),
262
+ "status": session.status,
263
+ "primary_work_type": primary,
264
+ }
265
+ )
266
+ except Exception as e:
267
+ warnings.append(f"Failed to load session {session_path.name}: {e}")
268
+ continue
269
+
270
+ metrics["recent_sessions"] = recent_sessions
271
+ except Exception as e:
272
+ warnings.append(f"Failed to load recent sessions: {e}")
273
+ metrics["recent_sessions"] = []
274
+
275
+ return AnalyticsProjectResult(metrics=metrics, warnings=warnings)
276
+
277
+ except AnalyticsOperationError:
278
+ raise
279
+ except Exception as e:
280
+ raise AnalyticsOperationError(f"Failed to analyze project: {e}")
281
+
282
+
283
+ def get_recommendations(*, graph_dir: Path) -> RecommendationsResult:
284
+ """
285
+ Get work recommendations based on project state.
286
+
287
+ Args:
288
+ graph_dir: Path to .htmlgraph directory
289
+
290
+ Returns:
291
+ RecommendationsResult with recommendations, reasoning, and warnings
292
+
293
+ Raises:
294
+ AnalyticsOperationError: If recommendations cannot be generated
295
+ """
296
+ warnings: list[str] = []
297
+
298
+ # Validate inputs
299
+ if not graph_dir.exists():
300
+ raise AnalyticsOperationError(f"Graph directory does not exist: {graph_dir}")
301
+
302
+ try:
303
+ # Initialize SDK
304
+ sdk = SDK(directory=str(graph_dir), agent="analytics-ops")
305
+
306
+ # Get recommendations
307
+ try:
308
+ task_recs = sdk.dep_analytics.recommend_next_tasks(agent_count=5)
309
+ recommendations = [
310
+ {
311
+ "id": rec.id,
312
+ "title": rec.title,
313
+ "priority": rec.priority,
314
+ "score": rec.score,
315
+ "reasons": rec.reasons,
316
+ "unlocks": rec.unlocks,
317
+ "estimated_effort": rec.estimated_effort,
318
+ }
319
+ for rec in task_recs.recommendations
320
+ ]
321
+ reasoning = {
322
+ "recommendation_count": len(task_recs.recommendations),
323
+ "parallel_suggestions": task_recs.parallel_suggestions,
324
+ }
325
+ except Exception as e:
326
+ raise AnalyticsOperationError(f"Failed to generate recommendations: {e}")
327
+
328
+ # Add contextual warnings based on recommendations
329
+ if not recommendations:
330
+ warnings.append("No recommendations available - project may be empty")
331
+
332
+ return RecommendationsResult(
333
+ recommendations=recommendations, reasoning=reasoning, warnings=warnings
334
+ )
335
+
336
+ except AnalyticsOperationError:
337
+ raise
338
+ except Exception as e:
339
+ raise AnalyticsOperationError(f"Failed to get recommendations: {e}")
@@ -0,0 +1,289 @@
1
+ from __future__ import annotations
2
+
3
+ """HtmlGraph bootstrap operations.
4
+
5
+ One-command setup to go from installation to first value in under 60 seconds.
6
+ This module provides functions for bootstrapping a project with HtmlGraph.
7
+
8
+ The bootstrap process includes:
9
+ 1. Auto-detecting project type (Python, Node, etc.)
10
+ 2. Creating .htmlgraph directory structure
11
+ 3. Initializing database with schema
12
+ 4. Installing Claude Code plugin hooks automatically
13
+ 5. Printing next steps for the user
14
+
15
+ This is designed for simplicity and speed - the minimal viable setup.
16
+ """
17
+
18
+
19
+ import json
20
+ import subprocess
21
+ from pathlib import Path
22
+ from typing import TYPE_CHECKING, Any
23
+
24
+ if TYPE_CHECKING:
25
+ from htmlgraph.cli.models import BootstrapConfig
26
+
27
+
28
+ def detect_project_type(project_dir: Path) -> str:
29
+ """
30
+ Auto-detect project type from files in directory.
31
+
32
+ Args:
33
+ project_dir: Project directory to inspect
34
+
35
+ Returns:
36
+ Detected project type: "python", "node", "multi", or "unknown"
37
+ """
38
+ # Check for Python project markers
39
+ has_python = any(
40
+ [
41
+ (project_dir / "pyproject.toml").exists(),
42
+ (project_dir / "setup.py").exists(),
43
+ (project_dir / "requirements.txt").exists(),
44
+ (project_dir / "Pipfile").exists(),
45
+ ]
46
+ )
47
+
48
+ # Check for Node project markers
49
+ has_node = (project_dir / "package.json").exists()
50
+
51
+ # Determine project type
52
+ if has_python and has_node:
53
+ return "multi"
54
+ elif has_python:
55
+ return "python"
56
+ elif has_node:
57
+ return "node"
58
+ else:
59
+ return "unknown"
60
+
61
+
62
+ def create_gitignore_template() -> str:
63
+ """
64
+ Create .gitignore template content for .htmlgraph directory.
65
+
66
+ Returns:
67
+ Gitignore template content
68
+ """
69
+ return """# HtmlGraph cache and regenerable files
70
+ .htmlgraph/htmlgraph.db
71
+ .htmlgraph/sessions/*.jsonl
72
+ .htmlgraph/events/*.jsonl
73
+ .htmlgraph/parent-activity.json
74
+ .htmlgraph/logs/
75
+ """
76
+
77
+
78
+ def check_already_initialized(project_dir: Path) -> bool:
79
+ """
80
+ Check if project is already initialized with HtmlGraph.
81
+
82
+ Args:
83
+ project_dir: Project directory to check
84
+
85
+ Returns:
86
+ True if already initialized, False otherwise
87
+ """
88
+ graph_dir = project_dir / ".htmlgraph"
89
+ return graph_dir.exists()
90
+
91
+
92
+ def create_bootstrap_structure(project_dir: Path) -> dict[str, list[str]]:
93
+ """
94
+ Create minimal .htmlgraph directory structure for bootstrap.
95
+
96
+ Args:
97
+ project_dir: Project directory
98
+
99
+ Returns:
100
+ Dictionary with lists of created directories and files
101
+ """
102
+ graph_dir = project_dir / ".htmlgraph"
103
+ created_dirs: list[str] = []
104
+ created_files: list[str] = []
105
+
106
+ # Create main .htmlgraph directory
107
+ if not graph_dir.exists():
108
+ graph_dir.mkdir(parents=True)
109
+ created_dirs.append(str(graph_dir))
110
+
111
+ # Create subdirectories
112
+ subdirs = [
113
+ "sessions",
114
+ "features",
115
+ "spikes",
116
+ "tracks",
117
+ "events",
118
+ "logs",
119
+ "logs/errors",
120
+ ]
121
+
122
+ for subdir in subdirs:
123
+ subdir_path = graph_dir / subdir
124
+ if not subdir_path.exists():
125
+ subdir_path.mkdir(parents=True)
126
+ created_dirs.append(str(subdir_path))
127
+
128
+ # Create .gitignore in .htmlgraph
129
+ gitignore = graph_dir / ".gitignore"
130
+ if not gitignore.exists():
131
+ gitignore.write_text(create_gitignore_template())
132
+ created_files.append(str(gitignore))
133
+
134
+ # Create config.json
135
+ config_file = graph_dir / "config.json"
136
+ if not config_file.exists():
137
+ config_data = {
138
+ "bootstrapped": True,
139
+ "version": "1.0",
140
+ }
141
+ config_file.write_text(json.dumps(config_data, indent=2) + "\n")
142
+ created_files.append(str(config_file))
143
+
144
+ return {"directories": created_dirs, "files": created_files}
145
+
146
+
147
+ def initialize_database(graph_dir: Path) -> str:
148
+ """
149
+ Initialize HtmlGraph database with schema.
150
+
151
+ Args:
152
+ graph_dir: Path to .htmlgraph directory
153
+
154
+ Returns:
155
+ Path to created database file
156
+ """
157
+ from htmlgraph.db.schema import HtmlGraphDB
158
+
159
+ db_path = graph_dir / "htmlgraph.db"
160
+
161
+ # Create database using HtmlGraphDB (auto-creates tables)
162
+ db = HtmlGraphDB(db_path=str(db_path))
163
+ db.disconnect()
164
+
165
+ return str(db_path)
166
+
167
+
168
+ def check_claude_code_available() -> bool:
169
+ """
170
+ Check if Claude Code CLI is available.
171
+
172
+ Returns:
173
+ True if claude command is available, False otherwise
174
+ """
175
+ try:
176
+ result = subprocess.run(
177
+ ["claude", "--version"],
178
+ capture_output=True,
179
+ check=False,
180
+ timeout=5,
181
+ )
182
+ return result.returncode == 0
183
+ except (subprocess.TimeoutExpired, FileNotFoundError):
184
+ return False
185
+
186
+
187
+ def get_next_steps(
188
+ project_type: str, has_claude: bool, plugin_installed: bool
189
+ ) -> list[str]:
190
+ """
191
+ Generate next steps message based on project state.
192
+
193
+ Args:
194
+ project_type: Detected project type
195
+ has_claude: Whether Claude Code CLI is available
196
+ plugin_installed: Whether plugin hooks were installed
197
+
198
+ Returns:
199
+ List of next step messages
200
+ """
201
+ steps = []
202
+
203
+ if has_claude:
204
+ if plugin_installed:
205
+ steps.append("1. Use Claude Code: Run 'claude --dev' in this project")
206
+ else:
207
+ steps.append(
208
+ "1. Install HtmlGraph plugin: Run 'claude plugin install htmlgraph'"
209
+ )
210
+ steps.append("2. Use Claude Code: Run 'claude --dev' in this project")
211
+ else:
212
+ steps.append(
213
+ "1. Install Claude Code CLI: Visit https://code.claude.com/docs/installation"
214
+ )
215
+ steps.append(
216
+ "2. Install HtmlGraph plugin: Run 'claude plugin install htmlgraph'"
217
+ )
218
+ steps.append("3. Use Claude Code: Run 'claude --dev' in this project")
219
+
220
+ steps.append(
221
+ f"{len(steps) + 1}. Track work: Create features with 'htmlgraph feature create \"Title\"'"
222
+ )
223
+ steps.append(f"{len(steps) + 1}. View progress: Run 'htmlgraph status'")
224
+ steps.append(
225
+ f"{len(steps) + 1}. See what Claude did: Run 'htmlgraph serve' and open http://localhost:8080"
226
+ )
227
+
228
+ return steps
229
+
230
+
231
+ def bootstrap_htmlgraph(config: BootstrapConfig) -> dict[str, Any]:
232
+ """
233
+ Bootstrap HtmlGraph in a project directory.
234
+
235
+ This is the main entry point for the bootstrap command.
236
+
237
+ Args:
238
+ config: BootstrapConfig with bootstrap settings
239
+
240
+ Returns:
241
+ Dictionary with bootstrap results
242
+ """
243
+ project_dir = Path(config.project_path).resolve()
244
+
245
+ # Check if already initialized
246
+ if check_already_initialized(project_dir):
247
+ # Ask user if they want to overwrite
248
+ print(f"\n⚠️ HtmlGraph already initialized in {project_dir}")
249
+ response = input("Do you want to reinitialize? (y/N): ").strip().lower()
250
+ if response not in ["y", "yes"]:
251
+ return {
252
+ "success": False,
253
+ "message": "Bootstrap cancelled - already initialized",
254
+ }
255
+
256
+ # Detect project type
257
+ project_type = detect_project_type(project_dir)
258
+
259
+ # Create directory structure
260
+ created = create_bootstrap_structure(project_dir)
261
+ graph_dir = project_dir / ".htmlgraph"
262
+
263
+ # Initialize database
264
+ db_path = initialize_database(graph_dir)
265
+ created["files"].append(db_path)
266
+
267
+ # Check for Claude Code
268
+ has_claude = check_claude_code_available()
269
+
270
+ # Check if plugin is already available (skip installation check for now)
271
+ plugin_installed = False
272
+ if not config.no_plugins and has_claude:
273
+ # We'll consider it "installed" if hooks can be configured
274
+ # The actual plugin installation happens via marketplace
275
+ plugin_installed = True
276
+
277
+ # Generate next steps
278
+ next_steps = get_next_steps(project_type, has_claude, plugin_installed)
279
+
280
+ return {
281
+ "success": True,
282
+ "project_type": project_type,
283
+ "graph_dir": str(graph_dir),
284
+ "directories_created": created["directories"],
285
+ "files_created": created["files"],
286
+ "has_claude": has_claude,
287
+ "plugin_installed": plugin_installed,
288
+ "next_steps": next_steps,
289
+ }