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,228 @@
1
+ """HtmlGraph initialization operations.
2
+
3
+ This module provides functions for initializing the .htmlgraph directory structure,
4
+ creating necessary files, and optionally installing Git hooks.
5
+
6
+ The initialization process includes:
7
+ 1. Directory structure creation (.htmlgraph with subdirectories)
8
+ 2. Database initialization (htmlgraph.db)
9
+ 3. Index creation (index.sqlite)
10
+ 4. Configuration files
11
+ 5. Optional Git hooks installation
12
+
13
+ Extracted from monolithic cmd_init() implementation for better maintainability.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import sqlite3
20
+ import subprocess
21
+ import sys
22
+ from pathlib import Path
23
+ from typing import TYPE_CHECKING
24
+
25
+ if TYPE_CHECKING:
26
+ from htmlgraph.cli.models import InitConfig, InitResult, ValidationResult
27
+
28
+
29
+ # Default collections to create
30
+ DEFAULT_COLLECTIONS = [
31
+ "features",
32
+ "bugs",
33
+ "chores",
34
+ "spikes",
35
+ "epics",
36
+ "phases",
37
+ "tracks",
38
+ "sessions",
39
+ "insights",
40
+ "metrics",
41
+ "agents",
42
+ "cigs",
43
+ ]
44
+
45
+ # Additional directories
46
+ ADDITIONAL_DIRECTORIES = [
47
+ "events",
48
+ "logs",
49
+ "archive-index",
50
+ "archives",
51
+ ]
52
+
53
+
54
+ def initialize_htmlgraph(config: InitConfig) -> InitResult:
55
+ """Initialize .htmlgraph directory structure.
56
+
57
+ Creates the standard HtmlGraph directory structure:
58
+ - .htmlgraph/
59
+ - features/
60
+ - sessions/
61
+ - spikes/
62
+ - bugs/
63
+ - tracks/
64
+ - events/
65
+ - logs/
66
+
67
+ Args:
68
+ config: Initialization configuration
69
+
70
+ Returns:
71
+ InitResult with success status and details
72
+ """
73
+ try:
74
+ # Resolve directory path
75
+ base_dir = Path(config.dir).resolve()
76
+ htmlgraph_dir = base_dir / ".htmlgraph"
77
+
78
+ created_dirs: list[str] = []
79
+ updated_files: list[str] = []
80
+
81
+ # Create main directory
82
+ if not htmlgraph_dir.exists():
83
+ htmlgraph_dir.mkdir(parents=True, exist_ok=True)
84
+ created_dirs.append(str(htmlgraph_dir))
85
+
86
+ # Create standard subdirectories
87
+ subdirs = [
88
+ "features",
89
+ "sessions",
90
+ "spikes",
91
+ "bugs",
92
+ "tracks",
93
+ "events",
94
+ "logs",
95
+ "logs/errors",
96
+ ]
97
+
98
+ for subdir in subdirs:
99
+ subdir_path = htmlgraph_dir / subdir
100
+ if not subdir_path.exists():
101
+ subdir_path.mkdir(parents=True, exist_ok=True)
102
+ created_dirs.append(str(subdir_path))
103
+
104
+ # Create .gitkeep in events directory (unless disabled)
105
+ if not config.no_events_keep:
106
+ events_dir = htmlgraph_dir / "events"
107
+ gitkeep_file = events_dir / ".gitkeep"
108
+ if not gitkeep_file.exists():
109
+ gitkeep_file.touch()
110
+ updated_files.append(str(gitkeep_file))
111
+
112
+ # Update .gitignore (unless disabled)
113
+ if not config.no_update_gitignore:
114
+ gitignore_path = base_dir / ".gitignore"
115
+ gitignore_entries = [
116
+ "# HtmlGraph cache files",
117
+ ".htmlgraph/index.sqlite",
118
+ ".htmlgraph/htmlgraph.db",
119
+ ".htmlgraph/sessions/*.jsonl",
120
+ ".htmlgraph/events/*.jsonl",
121
+ ".htmlgraph/parent-activity.json",
122
+ ]
123
+
124
+ # Read existing .gitignore
125
+ existing_entries: set[str] = set()
126
+ if gitignore_path.exists():
127
+ existing_entries = set(
128
+ line.strip() for line in gitignore_path.read_text().splitlines()
129
+ )
130
+
131
+ # Add missing entries
132
+ new_entries = [
133
+ entry for entry in gitignore_entries if entry not in existing_entries
134
+ ]
135
+
136
+ if new_entries:
137
+ mode = "a" if gitignore_path.exists() else "w"
138
+ with gitignore_path.open(mode) as f:
139
+ if mode == "a":
140
+ f.write("\n") # Add newline before appending
141
+ f.write("\n".join(new_entries) + "\n")
142
+ updated_files.append(str(gitignore_path))
143
+
144
+ # Initialize analytics index (unless disabled)
145
+ if not config.no_index:
146
+ from htmlgraph.analytics_index import AnalyticsIndex
147
+
148
+ index_path = htmlgraph_dir / "index.sqlite"
149
+ if not index_path.exists():
150
+ # Create empty index (will be populated on first use)
151
+ index = AnalyticsIndex(str(index_path))
152
+ index.ensure_schema()
153
+ created_dirs.append(str(index_path))
154
+
155
+ # Install Git hooks (if requested)
156
+ if config.install_hooks:
157
+ from htmlgraph.operations.hooks import install_hooks
158
+
159
+ try:
160
+ hooks_result = install_hooks(project_dir=base_dir, use_copy=False)
161
+ if hooks_result.installed:
162
+ updated_files.extend(hooks_result.installed)
163
+ if hooks_result.warnings:
164
+ for warning in hooks_result.warnings:
165
+ print(f"Warning: {warning}", file=sys.stderr)
166
+ except Exception as e:
167
+ # Non-fatal: hooks installation failed but init succeeded
168
+ print(f"Warning: Failed to install hooks: {e}", file=sys.stderr)
169
+
170
+ # Interactive setup wizard (if requested)
171
+ if config.interactive:
172
+ _run_interactive_setup(htmlgraph_dir)
173
+
174
+ # Build success message
175
+ message_parts = []
176
+ if created_dirs:
177
+ message_parts.append(f"Created {len(created_dirs)} directories")
178
+ if updated_files:
179
+ message_parts.append(f"Updated {len(updated_files)} files")
180
+
181
+ message = ", ".join(message_parts) if message_parts else "Already initialized"
182
+
183
+ return InitResult(
184
+ success=True,
185
+ directory=htmlgraph_dir,
186
+ message=message,
187
+ created_dirs=created_dirs,
188
+ updated_files=updated_files,
189
+ )
190
+
191
+ except Exception as e:
192
+ return InitResult(
193
+ success=False,
194
+ directory=Path(config.dir).resolve() / ".htmlgraph",
195
+ error=f"Initialization failed: {e}",
196
+ )
197
+
198
+
199
+ def _run_interactive_setup(htmlgraph_dir: Path) -> None:
200
+ """Run interactive setup wizard.
201
+
202
+ Args:
203
+ htmlgraph_dir: Path to .htmlgraph directory
204
+ """
205
+ print("\n=== HtmlGraph Interactive Setup ===\n")
206
+
207
+ # Ask about project name
208
+ project_name = input("Project name (optional, press Enter to skip): ").strip()
209
+
210
+ # Ask about default agent
211
+ default_agent = input("Default agent name (default: claude): ").strip()
212
+ if not default_agent:
213
+ default_agent = "claude"
214
+
215
+ # Create config file
216
+ config_file = htmlgraph_dir / "config.json"
217
+ if not config_file.exists():
218
+ import json
219
+
220
+ config_data = {}
221
+ if project_name:
222
+ config_data["project_name"] = project_name
223
+ config_data["default_agent"] = default_agent
224
+
225
+ config_file.write_text(json.dumps(config_data, indent=2) + "\n")
226
+ print(f"\n✓ Created config file: {config_file}")
227
+
228
+ print("\n✓ Interactive setup complete!\n")
@@ -0,0 +1,303 @@
1
+ from __future__ import annotations
2
+
3
+ """Server operations for HtmlGraph."""
4
+
5
+
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class ServerHandle:
13
+ url: str
14
+ port: int
15
+ host: str
16
+ server: Any | None = None
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class ServerStatus:
21
+ running: bool
22
+ url: str | None = None
23
+ port: int | None = None
24
+ host: str | None = None
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class ServerStartResult:
29
+ handle: ServerHandle
30
+ warnings: list[str]
31
+ config_used: dict[str, Any]
32
+
33
+
34
+ class ServerStartError(RuntimeError):
35
+ """Server failed to start."""
36
+
37
+
38
+ class PortInUseError(ServerStartError):
39
+ """Requested port is already in use."""
40
+
41
+
42
+ def start_server(
43
+ *,
44
+ port: int,
45
+ graph_dir: Path,
46
+ static_dir: Path,
47
+ host: str = "localhost",
48
+ watch: bool = True,
49
+ auto_port: bool = False,
50
+ ) -> ServerStartResult:
51
+ """
52
+ Start the HtmlGraph server with validated configuration.
53
+
54
+ Args:
55
+ port: Port to listen on
56
+ graph_dir: Directory containing graph data (.htmlgraph/)
57
+ static_dir: Directory for static files (index.html, etc.)
58
+ host: Host to bind to
59
+ watch: Enable file watching for auto-reload
60
+ auto_port: Automatically find available port if specified port is in use
61
+
62
+ Returns:
63
+ ServerStartResult with handle, warnings, and config used
64
+
65
+ Raises:
66
+ PortInUseError: If port is in use and auto_port=False
67
+ ServerStartError: If server fails to start
68
+ """
69
+ from http.server import HTTPServer
70
+
71
+ from htmlgraph.analytics_index import AnalyticsIndex
72
+ from htmlgraph.event_log import JsonlEventLog
73
+ from htmlgraph.file_watcher import GraphWatcher
74
+ from htmlgraph.graph import HtmlGraph
75
+ from htmlgraph.server import HtmlGraphAPIHandler, sync_dashboard_files
76
+
77
+ warnings: list[str] = []
78
+ original_port = port
79
+
80
+ # Handle auto-port selection
81
+ if auto_port and _check_port_in_use(port, host):
82
+ port = _find_available_port(port + 1)
83
+ warnings.append(f"Port {original_port} is in use, using {port} instead")
84
+
85
+ # Check if port is still in use (and we're not in auto-port mode)
86
+ if not auto_port and _check_port_in_use(port, host):
87
+ raise PortInUseError(
88
+ f"Port {port} is already in use. Use auto_port=True or choose a different port."
89
+ )
90
+
91
+ # Auto-sync dashboard files
92
+ try:
93
+ if sync_dashboard_files(static_dir):
94
+ warnings.append(
95
+ "Dashboard files out of sync, synced dashboard.html → index.html"
96
+ )
97
+ except PermissionError as e:
98
+ warnings.append(f"Unable to sync dashboard files: {e}")
99
+ except Exception as e:
100
+ warnings.append(f"Error during dashboard sync: {e}")
101
+
102
+ # Create graph directories
103
+ graph_dir.mkdir(parents=True, exist_ok=True)
104
+ for collection in HtmlGraphAPIHandler.COLLECTIONS:
105
+ (graph_dir / collection).mkdir(exist_ok=True)
106
+
107
+ # Copy default stylesheet
108
+ styles_dest = graph_dir / "styles.css"
109
+ if not styles_dest.exists():
110
+ styles_src = Path(__file__).parent.parent / "styles.css"
111
+ if styles_src.exists():
112
+ styles_dest.write_text(styles_src.read_text())
113
+
114
+ # Build analytics index if needed
115
+ events_dir = graph_dir / "events"
116
+ db_path = graph_dir / "index.sqlite"
117
+ index_needs_build = (
118
+ not db_path.exists() and events_dir.exists() and any(events_dir.glob("*.jsonl"))
119
+ )
120
+
121
+ if index_needs_build:
122
+ try:
123
+ log = JsonlEventLog(events_dir)
124
+ index = AnalyticsIndex(db_path)
125
+ events = (event for _, event in log.iter_events())
126
+ index.rebuild_from_events(events)
127
+ except Exception as e:
128
+ warnings.append(f"Failed to build analytics index: {e}")
129
+
130
+ # Configure handler
131
+ HtmlGraphAPIHandler.graph_dir = graph_dir
132
+ HtmlGraphAPIHandler.static_dir = static_dir
133
+ HtmlGraphAPIHandler.graphs = {}
134
+ HtmlGraphAPIHandler.analytics_db = None
135
+
136
+ # Start HTTP server
137
+ try:
138
+ server = HTTPServer((host, port), HtmlGraphAPIHandler)
139
+ except OSError as e:
140
+ if e.errno == 48 or "Address already in use" in str(e):
141
+ raise PortInUseError(f"Port {port} is already in use") from e
142
+ raise ServerStartError(f"Failed to start server: {e}") from e
143
+
144
+ # Start file watcher if enabled
145
+ watcher = None
146
+ if watch:
147
+
148
+ def get_graph(collection: str) -> HtmlGraph:
149
+ """Callback to get graph instance for a collection."""
150
+ handler = HtmlGraphAPIHandler
151
+ if collection not in handler.graphs:
152
+ collection_dir = handler.graph_dir / collection
153
+ handler.graphs[collection] = HtmlGraph(
154
+ collection_dir, stylesheet_path="../styles.css", auto_load=True
155
+ )
156
+ return handler.graphs[collection]
157
+
158
+ watcher = GraphWatcher(
159
+ graph_dir=graph_dir,
160
+ collections=HtmlGraphAPIHandler.COLLECTIONS,
161
+ get_graph_callback=get_graph,
162
+ )
163
+ watcher.start()
164
+
165
+ # Create handle
166
+ handle = ServerHandle(
167
+ url=f"http://{host}:{port}",
168
+ port=port,
169
+ host=host,
170
+ server={"httpserver": server, "watcher": watcher},
171
+ )
172
+
173
+ # Configuration used
174
+ config_used = {
175
+ "port": port,
176
+ "original_port": original_port,
177
+ "host": host,
178
+ "graph_dir": str(graph_dir),
179
+ "static_dir": str(static_dir),
180
+ "watch": watch,
181
+ "auto_port": auto_port,
182
+ }
183
+
184
+ return ServerStartResult(
185
+ handle=handle,
186
+ warnings=warnings,
187
+ config_used=config_used,
188
+ )
189
+
190
+
191
+ def stop_server(handle: ServerHandle) -> None:
192
+ """
193
+ Stop a running HtmlGraph server.
194
+
195
+ Args:
196
+ handle: ServerHandle returned from start_server()
197
+
198
+ Raises:
199
+ ServerStartError: If shutdown fails
200
+ """
201
+ if handle.server is None:
202
+ return
203
+
204
+ try:
205
+ # Extract server components
206
+ if isinstance(handle.server, dict):
207
+ httpserver = handle.server.get("httpserver")
208
+ watcher = handle.server.get("watcher")
209
+
210
+ # Stop file watcher first
211
+ if watcher is not None:
212
+ try:
213
+ watcher.stop()
214
+ except Exception:
215
+ pass # Best effort
216
+
217
+ # Shutdown HTTP server
218
+ if httpserver is not None:
219
+ httpserver.shutdown()
220
+ else:
221
+ # Assume it's the HTTPServer directly
222
+ handle.server.shutdown()
223
+ except Exception as e:
224
+ raise ServerStartError(f"Failed to stop server: {e}") from e
225
+
226
+
227
+ def get_server_status(handle: ServerHandle | None = None) -> ServerStatus:
228
+ """
229
+ Return server status for a handle or best-effort local check.
230
+
231
+ Args:
232
+ handle: Optional ServerHandle to check
233
+
234
+ Returns:
235
+ ServerStatus indicating whether server is running
236
+ """
237
+ if handle is None:
238
+ # No handle provided - cannot determine status
239
+ return ServerStatus(running=False)
240
+
241
+ # Check if server is running by testing the port
242
+ try:
243
+ is_running = not _check_port_in_use(handle.port, handle.host)
244
+ return ServerStatus(
245
+ running=is_running,
246
+ url=handle.url if is_running else None,
247
+ port=handle.port if is_running else None,
248
+ host=handle.host if is_running else None,
249
+ )
250
+ except Exception:
251
+ return ServerStatus(running=False)
252
+
253
+
254
+ # Helper functions (private)
255
+
256
+
257
+ def _check_port_in_use(port: int, host: str = "localhost") -> bool:
258
+ """
259
+ Check if a port is already in use.
260
+
261
+ Args:
262
+ port: Port number to check
263
+ host: Host to check on
264
+
265
+ Returns:
266
+ True if port is in use, False otherwise
267
+ """
268
+ import socket
269
+
270
+ try:
271
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
272
+ s.bind((host, port))
273
+ return False
274
+ except OSError:
275
+ return True
276
+
277
+
278
+ def _find_available_port(start_port: int = 8080, max_attempts: int = 10) -> int:
279
+ """
280
+ Find an available port starting from start_port.
281
+
282
+ Args:
283
+ start_port: Port to start searching from
284
+ max_attempts: Maximum number of ports to try
285
+
286
+ Returns:
287
+ Available port number
288
+
289
+ Raises:
290
+ ServerStartError: If no available port found in range
291
+ """
292
+ import socket
293
+
294
+ for port in range(start_port, start_port + max_attempts):
295
+ try:
296
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
297
+ s.bind(("", port))
298
+ return port
299
+ except OSError:
300
+ continue
301
+ raise ServerStartError(
302
+ f"No available ports found in range {start_port}-{start_port + max_attempts}"
303
+ )
@@ -0,0 +1,58 @@
1
+ """Orchestration utilities for multi-agent coordination."""
2
+
3
+ from .headless_spawner import AIResult, HeadlessSpawner
4
+ from .model_selection import (
5
+ BudgetMode,
6
+ ComplexityLevel,
7
+ ModelSelection,
8
+ TaskType,
9
+ get_fallback_chain,
10
+ select_model,
11
+ )
12
+ from .spawner_event_tracker import SpawnerEventTracker, create_tracker_from_env
13
+
14
+ # Export modular spawners for advanced usage
15
+ from .spawners import (
16
+ BaseSpawner,
17
+ ClaudeSpawner,
18
+ CodexSpawner,
19
+ CopilotSpawner,
20
+ GeminiSpawner,
21
+ )
22
+ from .task_coordination import (
23
+ delegate_with_id,
24
+ generate_task_id,
25
+ get_results_by_task_id,
26
+ parallel_delegate,
27
+ save_task_results,
28
+ validate_and_save,
29
+ )
30
+
31
+ __all__ = [
32
+ # Headless AI spawning (unified interface)
33
+ "HeadlessSpawner",
34
+ "AIResult",
35
+ # Modular spawner implementations
36
+ "BaseSpawner",
37
+ "GeminiSpawner",
38
+ "CodexSpawner",
39
+ "CopilotSpawner",
40
+ "ClaudeSpawner",
41
+ # Spawner event tracking
42
+ "SpawnerEventTracker",
43
+ "create_tracker_from_env",
44
+ # Model selection
45
+ "ModelSelection",
46
+ "TaskType",
47
+ "ComplexityLevel",
48
+ "BudgetMode",
49
+ "select_model",
50
+ "get_fallback_chain",
51
+ # Task coordination
52
+ "delegate_with_id",
53
+ "generate_task_id",
54
+ "get_results_by_task_id",
55
+ "parallel_delegate",
56
+ "save_task_results",
57
+ "validate_and_save",
58
+ ]