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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
  2. htmlgraph/.htmlgraph/agents.json +72 -0
  3. htmlgraph/.htmlgraph/htmlgraph.db +0 -0
  4. htmlgraph/__init__.py +173 -17
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +127 -0
  7. htmlgraph/agent_registry.py +45 -30
  8. htmlgraph/agents.py +160 -107
  9. htmlgraph/analytics/__init__.py +9 -2
  10. htmlgraph/analytics/cli.py +190 -51
  11. htmlgraph/analytics/cost_analyzer.py +391 -0
  12. htmlgraph/analytics/cost_monitor.py +664 -0
  13. htmlgraph/analytics/cost_reporter.py +675 -0
  14. htmlgraph/analytics/cross_session.py +617 -0
  15. htmlgraph/analytics/dependency.py +192 -100
  16. htmlgraph/analytics/pattern_learning.py +771 -0
  17. htmlgraph/analytics/session_graph.py +707 -0
  18. htmlgraph/analytics/strategic/__init__.py +80 -0
  19. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  20. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  21. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  22. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  23. htmlgraph/analytics/work_type.py +190 -14
  24. htmlgraph/analytics_index.py +135 -51
  25. htmlgraph/api/__init__.py +3 -0
  26. htmlgraph/api/cost_alerts_websocket.py +416 -0
  27. htmlgraph/api/main.py +2498 -0
  28. htmlgraph/api/static/htmx.min.js +1 -0
  29. htmlgraph/api/static/style-redesign.css +1344 -0
  30. htmlgraph/api/static/style.css +1079 -0
  31. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  32. htmlgraph/api/templates/dashboard.html +794 -0
  33. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  34. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  35. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  36. htmlgraph/api/templates/partials/agents.html +317 -0
  37. htmlgraph/api/templates/partials/event-traces.html +373 -0
  38. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  39. htmlgraph/api/templates/partials/features.html +578 -0
  40. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  41. htmlgraph/api/templates/partials/metrics.html +346 -0
  42. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  43. htmlgraph/api/templates/partials/orchestration.html +198 -0
  44. htmlgraph/api/templates/partials/spawners.html +375 -0
  45. htmlgraph/api/templates/partials/work-items.html +613 -0
  46. htmlgraph/api/websocket.py +538 -0
  47. htmlgraph/archive/__init__.py +24 -0
  48. htmlgraph/archive/bloom.py +234 -0
  49. htmlgraph/archive/fts.py +297 -0
  50. htmlgraph/archive/manager.py +583 -0
  51. htmlgraph/archive/search.py +244 -0
  52. htmlgraph/atomic_ops.py +560 -0
  53. htmlgraph/attribute_index.py +208 -0
  54. htmlgraph/bounded_paths.py +539 -0
  55. htmlgraph/builders/__init__.py +14 -0
  56. htmlgraph/builders/base.py +118 -29
  57. htmlgraph/builders/bug.py +150 -0
  58. htmlgraph/builders/chore.py +119 -0
  59. htmlgraph/builders/epic.py +150 -0
  60. htmlgraph/builders/feature.py +31 -6
  61. htmlgraph/builders/insight.py +195 -0
  62. htmlgraph/builders/metric.py +217 -0
  63. htmlgraph/builders/pattern.py +202 -0
  64. htmlgraph/builders/phase.py +162 -0
  65. htmlgraph/builders/spike.py +52 -19
  66. htmlgraph/builders/track.py +148 -72
  67. htmlgraph/cigs/__init__.py +81 -0
  68. htmlgraph/cigs/autonomy.py +385 -0
  69. htmlgraph/cigs/cost.py +475 -0
  70. htmlgraph/cigs/messages_basic.py +472 -0
  71. htmlgraph/cigs/messaging.py +365 -0
  72. htmlgraph/cigs/models.py +771 -0
  73. htmlgraph/cigs/pattern_storage.py +427 -0
  74. htmlgraph/cigs/patterns.py +503 -0
  75. htmlgraph/cigs/posttool_analyzer.py +234 -0
  76. htmlgraph/cigs/reporter.py +818 -0
  77. htmlgraph/cigs/tracker.py +317 -0
  78. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  79. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  80. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  81. htmlgraph/cli/__init__.py +42 -0
  82. htmlgraph/cli/__main__.py +6 -0
  83. htmlgraph/cli/analytics.py +1424 -0
  84. htmlgraph/cli/base.py +685 -0
  85. htmlgraph/cli/constants.py +206 -0
  86. htmlgraph/cli/core.py +954 -0
  87. htmlgraph/cli/main.py +147 -0
  88. htmlgraph/cli/models.py +475 -0
  89. htmlgraph/cli/templates/__init__.py +1 -0
  90. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  91. htmlgraph/cli/work/__init__.py +239 -0
  92. htmlgraph/cli/work/browse.py +115 -0
  93. htmlgraph/cli/work/features.py +568 -0
  94. htmlgraph/cli/work/orchestration.py +676 -0
  95. htmlgraph/cli/work/report.py +728 -0
  96. htmlgraph/cli/work/sessions.py +466 -0
  97. htmlgraph/cli/work/snapshot.py +559 -0
  98. htmlgraph/cli/work/tracks.py +486 -0
  99. htmlgraph/cli_commands/__init__.py +1 -0
  100. htmlgraph/cli_commands/feature.py +195 -0
  101. htmlgraph/cli_framework.py +115 -0
  102. htmlgraph/collections/__init__.py +18 -0
  103. htmlgraph/collections/base.py +415 -98
  104. htmlgraph/collections/bug.py +53 -0
  105. htmlgraph/collections/chore.py +53 -0
  106. htmlgraph/collections/epic.py +53 -0
  107. htmlgraph/collections/feature.py +12 -26
  108. htmlgraph/collections/insight.py +100 -0
  109. htmlgraph/collections/metric.py +92 -0
  110. htmlgraph/collections/pattern.py +97 -0
  111. htmlgraph/collections/phase.py +53 -0
  112. htmlgraph/collections/session.py +194 -0
  113. htmlgraph/collections/spike.py +56 -16
  114. htmlgraph/collections/task_delegation.py +241 -0
  115. htmlgraph/collections/todo.py +511 -0
  116. htmlgraph/collections/traces.py +487 -0
  117. htmlgraph/config/cost_models.json +56 -0
  118. htmlgraph/config.py +190 -0
  119. htmlgraph/context_analytics.py +344 -0
  120. htmlgraph/converter.py +216 -28
  121. htmlgraph/cost_analysis/__init__.py +5 -0
  122. htmlgraph/cost_analysis/analyzer.py +438 -0
  123. htmlgraph/dashboard.html +2406 -307
  124. htmlgraph/dashboard.html.backup +6592 -0
  125. htmlgraph/dashboard.html.bak +7181 -0
  126. htmlgraph/dashboard.html.bak2 +7231 -0
  127. htmlgraph/dashboard.html.bak3 +7232 -0
  128. htmlgraph/db/__init__.py +38 -0
  129. htmlgraph/db/queries.py +790 -0
  130. htmlgraph/db/schema.py +1788 -0
  131. htmlgraph/decorators.py +317 -0
  132. htmlgraph/dependency_models.py +19 -2
  133. htmlgraph/deploy.py +142 -125
  134. htmlgraph/deployment_models.py +474 -0
  135. htmlgraph/docs/API_REFERENCE.md +841 -0
  136. htmlgraph/docs/HTTP_API.md +750 -0
  137. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  138. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  139. htmlgraph/docs/README.md +532 -0
  140. htmlgraph/docs/__init__.py +77 -0
  141. htmlgraph/docs/docs_version.py +55 -0
  142. htmlgraph/docs/metadata.py +93 -0
  143. htmlgraph/docs/migrations.py +232 -0
  144. htmlgraph/docs/template_engine.py +143 -0
  145. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  146. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  147. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  148. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  149. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  150. htmlgraph/docs/version_check.py +163 -0
  151. htmlgraph/edge_index.py +182 -27
  152. htmlgraph/error_handler.py +544 -0
  153. htmlgraph/event_log.py +100 -52
  154. htmlgraph/event_migration.py +13 -4
  155. htmlgraph/exceptions.py +49 -0
  156. htmlgraph/file_watcher.py +101 -28
  157. htmlgraph/find_api.py +75 -63
  158. htmlgraph/git_events.py +145 -63
  159. htmlgraph/graph.py +1122 -106
  160. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  161. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  162. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  163. htmlgraph/hooks/__init__.py +45 -0
  164. htmlgraph/hooks/bootstrap.py +169 -0
  165. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  166. htmlgraph/hooks/concurrent_sessions.py +208 -0
  167. htmlgraph/hooks/context.py +350 -0
  168. htmlgraph/hooks/drift_handler.py +525 -0
  169. htmlgraph/hooks/event_tracker.py +1314 -0
  170. htmlgraph/hooks/git_commands.py +175 -0
  171. htmlgraph/hooks/hooks-config.example.json +12 -0
  172. htmlgraph/hooks/installer.py +343 -0
  173. htmlgraph/hooks/orchestrator.py +674 -0
  174. htmlgraph/hooks/orchestrator_reflector.py +223 -0
  175. htmlgraph/hooks/post-checkout.sh +28 -0
  176. htmlgraph/hooks/post-commit.sh +24 -0
  177. htmlgraph/hooks/post-merge.sh +26 -0
  178. htmlgraph/hooks/post_tool_use_failure.py +273 -0
  179. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  180. htmlgraph/hooks/posttooluse.py +408 -0
  181. htmlgraph/hooks/pre-commit.sh +94 -0
  182. htmlgraph/hooks/pre-push.sh +28 -0
  183. htmlgraph/hooks/pretooluse.py +819 -0
  184. htmlgraph/hooks/prompt_analyzer.py +637 -0
  185. htmlgraph/hooks/session_handler.py +668 -0
  186. htmlgraph/hooks/session_summary.py +395 -0
  187. htmlgraph/hooks/state_manager.py +504 -0
  188. htmlgraph/hooks/subagent_detection.py +202 -0
  189. htmlgraph/hooks/subagent_stop.py +369 -0
  190. htmlgraph/hooks/task_enforcer.py +255 -0
  191. htmlgraph/hooks/task_validator.py +177 -0
  192. htmlgraph/hooks/validator.py +628 -0
  193. htmlgraph/ids.py +41 -27
  194. htmlgraph/index.d.ts +286 -0
  195. htmlgraph/learning.py +767 -0
  196. htmlgraph/mcp_server.py +69 -23
  197. htmlgraph/models.py +1586 -87
  198. htmlgraph/operations/README.md +62 -0
  199. htmlgraph/operations/__init__.py +79 -0
  200. htmlgraph/operations/analytics.py +339 -0
  201. htmlgraph/operations/bootstrap.py +289 -0
  202. htmlgraph/operations/events.py +244 -0
  203. htmlgraph/operations/fastapi_server.py +231 -0
  204. htmlgraph/operations/hooks.py +350 -0
  205. htmlgraph/operations/initialization.py +597 -0
  206. htmlgraph/operations/initialization.py.backup +228 -0
  207. htmlgraph/operations/server.py +303 -0
  208. htmlgraph/orchestration/__init__.py +58 -0
  209. htmlgraph/orchestration/claude_launcher.py +179 -0
  210. htmlgraph/orchestration/command_builder.py +72 -0
  211. htmlgraph/orchestration/headless_spawner.py +281 -0
  212. htmlgraph/orchestration/live_events.py +377 -0
  213. htmlgraph/orchestration/model_selection.py +327 -0
  214. htmlgraph/orchestration/plugin_manager.py +140 -0
  215. htmlgraph/orchestration/prompts.py +137 -0
  216. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  217. htmlgraph/orchestration/spawners/__init__.py +16 -0
  218. htmlgraph/orchestration/spawners/base.py +194 -0
  219. htmlgraph/orchestration/spawners/claude.py +173 -0
  220. htmlgraph/orchestration/spawners/codex.py +435 -0
  221. htmlgraph/orchestration/spawners/copilot.py +294 -0
  222. htmlgraph/orchestration/spawners/gemini.py +471 -0
  223. htmlgraph/orchestration/subprocess_runner.py +36 -0
  224. htmlgraph/orchestration/task_coordination.py +343 -0
  225. htmlgraph/orchestration.md +563 -0
  226. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  227. htmlgraph/orchestrator.py +669 -0
  228. htmlgraph/orchestrator_config.py +357 -0
  229. htmlgraph/orchestrator_mode.py +328 -0
  230. htmlgraph/orchestrator_validator.py +133 -0
  231. htmlgraph/parallel.py +646 -0
  232. htmlgraph/parser.py +160 -35
  233. htmlgraph/path_query.py +608 -0
  234. htmlgraph/pattern_matcher.py +636 -0
  235. htmlgraph/planning.py +147 -52
  236. htmlgraph/pydantic_models.py +476 -0
  237. htmlgraph/quality_gates.py +350 -0
  238. htmlgraph/query_builder.py +109 -72
  239. htmlgraph/query_composer.py +509 -0
  240. htmlgraph/reflection.py +443 -0
  241. htmlgraph/refs.py +344 -0
  242. htmlgraph/repo_hash.py +512 -0
  243. htmlgraph/repositories/__init__.py +292 -0
  244. htmlgraph/repositories/analytics_repository.py +455 -0
  245. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  246. htmlgraph/repositories/feature_repository.py +581 -0
  247. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  248. htmlgraph/repositories/feature_repository_memory.py +607 -0
  249. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  250. htmlgraph/repositories/filter_service.py +620 -0
  251. htmlgraph/repositories/filter_service_standard.py +445 -0
  252. htmlgraph/repositories/shared_cache.py +621 -0
  253. htmlgraph/repositories/shared_cache_memory.py +395 -0
  254. htmlgraph/repositories/track_repository.py +552 -0
  255. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  256. htmlgraph/repositories/track_repository_memory.py +508 -0
  257. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  258. htmlgraph/routing.py +8 -19
  259. htmlgraph/scripts/deploy.py +1 -2
  260. htmlgraph/sdk/__init__.py +398 -0
  261. htmlgraph/sdk/__init__.pyi +14 -0
  262. htmlgraph/sdk/analytics/__init__.py +19 -0
  263. htmlgraph/sdk/analytics/engine.py +155 -0
  264. htmlgraph/sdk/analytics/helpers.py +178 -0
  265. htmlgraph/sdk/analytics/registry.py +109 -0
  266. htmlgraph/sdk/base.py +484 -0
  267. htmlgraph/sdk/constants.py +216 -0
  268. htmlgraph/sdk/core.pyi +308 -0
  269. htmlgraph/sdk/discovery.py +120 -0
  270. htmlgraph/sdk/help/__init__.py +12 -0
  271. htmlgraph/sdk/help/mixin.py +699 -0
  272. htmlgraph/sdk/mixins/__init__.py +15 -0
  273. htmlgraph/sdk/mixins/attribution.py +113 -0
  274. htmlgraph/sdk/mixins/mixin.py +410 -0
  275. htmlgraph/sdk/operations/__init__.py +12 -0
  276. htmlgraph/sdk/operations/mixin.py +427 -0
  277. htmlgraph/sdk/orchestration/__init__.py +17 -0
  278. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  279. htmlgraph/sdk/orchestration/spawner.py +204 -0
  280. htmlgraph/sdk/planning/__init__.py +19 -0
  281. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  282. htmlgraph/sdk/planning/mixin.py +211 -0
  283. htmlgraph/sdk/planning/parallel.py +186 -0
  284. htmlgraph/sdk/planning/queue.py +210 -0
  285. htmlgraph/sdk/planning/recommendations.py +87 -0
  286. htmlgraph/sdk/planning/smart_planning.py +319 -0
  287. htmlgraph/sdk/session/__init__.py +19 -0
  288. htmlgraph/sdk/session/continuity.py +57 -0
  289. htmlgraph/sdk/session/handoff.py +110 -0
  290. htmlgraph/sdk/session/info.py +309 -0
  291. htmlgraph/sdk/session/manager.py +103 -0
  292. htmlgraph/sdk/strategic/__init__.py +26 -0
  293. htmlgraph/sdk/strategic/mixin.py +563 -0
  294. htmlgraph/server.py +685 -180
  295. htmlgraph/services/__init__.py +10 -0
  296. htmlgraph/services/claiming.py +199 -0
  297. htmlgraph/session_hooks.py +300 -0
  298. htmlgraph/session_manager.py +1392 -175
  299. htmlgraph/session_registry.py +587 -0
  300. htmlgraph/session_state.py +436 -0
  301. htmlgraph/session_warning.py +201 -0
  302. htmlgraph/sessions/__init__.py +23 -0
  303. htmlgraph/sessions/handoff.py +756 -0
  304. htmlgraph/setup.py +34 -17
  305. htmlgraph/spike_index.py +143 -0
  306. htmlgraph/sync_docs.py +12 -15
  307. htmlgraph/system_prompts.py +450 -0
  308. htmlgraph/templates/AGENTS.md.template +366 -0
  309. htmlgraph/templates/CLAUDE.md.template +97 -0
  310. htmlgraph/templates/GEMINI.md.template +87 -0
  311. htmlgraph/templates/orchestration-view.html +350 -0
  312. htmlgraph/track_builder.py +146 -15
  313. htmlgraph/track_manager.py +69 -21
  314. htmlgraph/transcript.py +890 -0
  315. htmlgraph/transcript_analytics.py +699 -0
  316. htmlgraph/types.py +323 -0
  317. htmlgraph/validation.py +115 -0
  318. htmlgraph/watch.py +8 -5
  319. htmlgraph/work_type_utils.py +3 -2
  320. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
  321. htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
  322. htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
  323. htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
  324. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
  325. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  326. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  327. htmlgraph/cli.py +0 -2688
  328. htmlgraph/sdk.py +0 -709
  329. htmlgraph-0.9.3.dist-info/RECORD +0 -61
  330. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  331. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,474 @@
1
+ """
2
+ Pydantic Models for Deployment Configuration and Operations
3
+
4
+ These models provide validated, type-safe deployment configurations with:
5
+ - Semantic version validation
6
+ - Configuration schema validation
7
+ - Automatic field documentation
8
+ - JSON schema generation for tooling
9
+ """
10
+
11
+ import re
12
+ from enum import Enum
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from pydantic import BaseModel, Field, field_validator, model_validator
17
+
18
+
19
+ class DeploymentStep(str, Enum):
20
+ """Available deployment steps."""
21
+
22
+ GIT_PUSH = "git-push"
23
+ BUILD = "build"
24
+ PYPI_PUBLISH = "pypi-publish"
25
+ LOCAL_INSTALL = "local-install"
26
+ UPDATE_PLUGINS = "update-plugins"
27
+
28
+
29
+ class SemanticVersion(BaseModel):
30
+ """Semantic version string (major.minor.patch)."""
31
+
32
+ major: int = Field(..., ge=0, description="Major version number")
33
+ minor: int = Field(..., ge=0, description="Minor version number")
34
+ patch: int = Field(..., ge=0, description="Patch version number")
35
+ prerelease: str | None = Field(
36
+ None, description="Pre-release identifier (alpha, beta, rc)"
37
+ )
38
+ build: str | None = Field(None, description="Build metadata")
39
+
40
+ @classmethod
41
+ def from_string(cls, version_str: str) -> "SemanticVersion":
42
+ """Parse semantic version from string.
43
+
44
+ Examples:
45
+ - "1.2.3"
46
+ - "2.0.0-alpha.1"
47
+ - "3.1.4-beta+build.123"
48
+ """
49
+ # SemVer regex pattern
50
+ pattern = r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
51
+
52
+ match = re.match(pattern, version_str)
53
+ if not match:
54
+ raise ValueError(f"Invalid semantic version: {version_str}")
55
+
56
+ return cls(
57
+ major=int(match.group("major")),
58
+ minor=int(match.group("minor")),
59
+ patch=int(match.group("patch")),
60
+ prerelease=match.group("prerelease"),
61
+ build=match.group("build"),
62
+ )
63
+
64
+ def to_string(self) -> str:
65
+ """Convert to semantic version string."""
66
+ version = f"{self.major}.{self.minor}.{self.patch}"
67
+ if self.prerelease:
68
+ version += f"-{self.prerelease}"
69
+ if self.build:
70
+ version += f"+{self.build}"
71
+ return version
72
+
73
+ def __str__(self) -> str:
74
+ """
75
+ Convert version to string representation.
76
+
77
+ Enables using str() and string formatting on SemanticVersion instances.
78
+
79
+ Returns:
80
+ str: Version string in semver format (e.g., "1.2.3", "2.0.0-beta.1")
81
+
82
+ Example:
83
+ >>> version = SemanticVersion(1, 2, 3)
84
+ >>> str(version)
85
+ '1.2.3'
86
+ >>> print(f"Version: {version}")
87
+ Version: 1.2.3
88
+ >>> version_pre = SemanticVersion(2, 0, 0, prerelease="beta.1")
89
+ >>> str(version_pre)
90
+ '2.0.0-beta.1'
91
+ """
92
+ return self.to_string()
93
+
94
+ def __lt__(self, other: "SemanticVersion") -> bool:
95
+ """
96
+ Compare versions for sorting.
97
+
98
+ Enables using <, >, <=, >= operators and sorting SemanticVersion instances.
99
+ Follows semantic versioning precedence rules:
100
+ - Major, minor, patch compared numerically
101
+ - Prerelease versions have lower precedence than release versions
102
+ - Build metadata is ignored in comparisons
103
+
104
+ Args:
105
+ other: SemanticVersion to compare with
106
+
107
+ Returns:
108
+ bool: True if self is less than other
109
+
110
+ Example:
111
+ >>> v1 = SemanticVersion(1, 0, 0)
112
+ >>> v2 = SemanticVersion(2, 0, 0)
113
+ >>> v1 < v2
114
+ True
115
+ >>> v1_pre = SemanticVersion(1, 0, 0, prerelease="beta.1")
116
+ >>> v1_pre < v1
117
+ True
118
+ >>> versions = [v2, v1_pre, v1]
119
+ >>> sorted(versions)
120
+ [SemanticVersion(1, 0, 0, prerelease='beta.1'), SemanticVersion(1, 0, 0), SemanticVersion(2, 0, 0)]
121
+ """
122
+ if self.major != other.major:
123
+ return self.major < other.major
124
+ if self.minor != other.minor:
125
+ return self.minor < other.minor
126
+ if self.patch != other.patch:
127
+ return self.patch < other.patch
128
+
129
+ # Prerelease versions have lower precedence
130
+ if self.prerelease and not other.prerelease:
131
+ return True
132
+ if not self.prerelease and other.prerelease:
133
+ return False
134
+
135
+ return False
136
+
137
+
138
+ class GitConfig(BaseModel):
139
+ """Git configuration for deployment."""
140
+
141
+ branch: str = Field(default="main", description="Git branch to push to")
142
+ remote: str = Field(default="origin", description="Git remote name")
143
+ push_tags: bool = Field(default=True, description="Push tags with commits")
144
+
145
+ @field_validator("branch")
146
+ @classmethod
147
+ def validate_branch(cls, v: str) -> str:
148
+ """Validate git branch name."""
149
+ if not v or not v.strip():
150
+ raise ValueError("Branch name cannot be empty")
151
+ # Basic validation - no spaces, special characters
152
+ if re.search(r"[^\w\-/.]", v):
153
+ raise ValueError(f"Invalid branch name: {v}")
154
+ return v
155
+
156
+
157
+ class BuildConfig(BaseModel):
158
+ """Build configuration for package."""
159
+
160
+ command: str = Field(default="uv build", description="Build command to execute")
161
+ clean_dist: bool = Field(default=True, description="Clean dist/ before building")
162
+ verify_artifacts: bool = Field(
163
+ default=True, description="Verify build artifacts exist"
164
+ )
165
+
166
+ @field_validator("command")
167
+ @classmethod
168
+ def validate_command(cls, v: str) -> str:
169
+ """Validate build command."""
170
+ if not v or not v.strip():
171
+ raise ValueError("Build command cannot be empty")
172
+ return v.strip()
173
+
174
+
175
+ class PyPIConfig(BaseModel):
176
+ """PyPI publishing configuration."""
177
+
178
+ token_env_var: str = Field(
179
+ default="PyPI_API_TOKEN",
180
+ description="Environment variable containing PyPI API token",
181
+ )
182
+ wait_after_publish: int = Field(
183
+ default=10,
184
+ ge=0,
185
+ le=60,
186
+ description="Seconds to wait after publishing (for PyPI to process)",
187
+ )
188
+ verify_publication: bool = Field(
189
+ default=True, description="Verify package appears on PyPI after publishing"
190
+ )
191
+
192
+ @field_validator("token_env_var")
193
+ @classmethod
194
+ def validate_token_var(cls, v: str) -> str:
195
+ """Validate environment variable name."""
196
+ if not v or not v.strip():
197
+ raise ValueError("Token environment variable name cannot be empty")
198
+ # Must be valid env var name
199
+ if not re.match(r"^[A-Z_][A-Z0-9_]*$", v):
200
+ raise ValueError(f"Invalid environment variable name: {v}")
201
+ return v
202
+
203
+
204
+ class PluginConfig(BaseModel):
205
+ """Plugin update configuration."""
206
+
207
+ name: str = Field(..., description="Plugin name")
208
+ command: str = Field(
209
+ ..., description="Update command with {package} and {version} placeholders"
210
+ )
211
+ required: bool = Field(
212
+ default=False, description="Fail deployment if plugin update fails"
213
+ )
214
+
215
+ @field_validator("command")
216
+ @classmethod
217
+ def validate_command(cls, v: str) -> str:
218
+ """Validate plugin update command."""
219
+ if not v or not v.strip():
220
+ raise ValueError("Plugin command cannot be empty")
221
+
222
+ # Check for required placeholders
223
+ if "{package}" not in v and "{version}" not in v:
224
+ raise ValueError(
225
+ "Plugin command must contain {package} or {version} placeholder"
226
+ )
227
+
228
+ return v.strip()
229
+
230
+
231
+ class DeploymentHooks(BaseModel):
232
+ """Custom deployment hooks."""
233
+
234
+ pre_build: list[str] = Field(
235
+ default_factory=list, description="Commands to run before build"
236
+ )
237
+ post_build: list[str] = Field(
238
+ default_factory=list, description="Commands to run after build"
239
+ )
240
+ pre_publish: list[str] = Field(
241
+ default_factory=list, description="Commands to run before publish"
242
+ )
243
+ post_publish: list[str] = Field(
244
+ default_factory=list, description="Commands to run after publish"
245
+ )
246
+
247
+ @field_validator("pre_build", "post_build", "pre_publish", "post_publish")
248
+ @classmethod
249
+ def validate_hooks(cls, v: list[str]) -> list[str]:
250
+ """Validate hook commands."""
251
+ return [cmd.strip() for cmd in v if cmd.strip()]
252
+
253
+
254
+ class DeploymentConfig(BaseModel):
255
+ """Complete deployment configuration with validation.
256
+
257
+ Example:
258
+ ```python
259
+ config = DeploymentConfig(
260
+ project_name="htmlgraph",
261
+ pypi_package="htmlgraph",
262
+ version="0.10.0",
263
+ steps=[
264
+ DeploymentStep.GIT_PUSH,
265
+ DeploymentStep.BUILD,
266
+ DeploymentStep.PYPI_PUBLISH
267
+ ]
268
+ )
269
+ ```
270
+ """
271
+
272
+ # Project info
273
+ project_name: str = Field(..., min_length=1, description="Project name")
274
+ pypi_package: str | None = Field(
275
+ None, description="PyPI package name (if different from project)"
276
+ )
277
+ version: str | None = Field(None, description="Version to deploy")
278
+
279
+ # Deployment steps
280
+ steps: list[DeploymentStep] = Field(
281
+ default=[
282
+ DeploymentStep.GIT_PUSH,
283
+ DeploymentStep.BUILD,
284
+ DeploymentStep.PYPI_PUBLISH,
285
+ DeploymentStep.LOCAL_INSTALL,
286
+ DeploymentStep.UPDATE_PLUGINS,
287
+ ],
288
+ description="Deployment steps to execute in order",
289
+ )
290
+
291
+ # Component configs
292
+ git: GitConfig = Field(default_factory=GitConfig, description="Git configuration")
293
+ build: BuildConfig = Field(
294
+ default_factory=BuildConfig, description="Build configuration"
295
+ )
296
+ pypi: PyPIConfig = Field(
297
+ default_factory=PyPIConfig, description="PyPI configuration"
298
+ )
299
+
300
+ # Plugins and hooks
301
+ plugins: list[PluginConfig] = Field(
302
+ default_factory=list, description="Plugin update configs"
303
+ )
304
+ hooks: DeploymentHooks = Field(
305
+ default_factory=DeploymentHooks, description="Custom deployment hooks"
306
+ )
307
+
308
+ # Flags
309
+ dry_run: bool = Field(
310
+ default=False, description="Simulate deployment without executing"
311
+ )
312
+ skip_confirmations: bool = Field(
313
+ default=False, description="Skip confirmation prompts"
314
+ )
315
+
316
+ @field_validator("project_name")
317
+ @classmethod
318
+ def validate_project_name(cls, v: str) -> str:
319
+ """Validate project name."""
320
+ if not v or not v.strip():
321
+ raise ValueError("Project name cannot be empty")
322
+
323
+ # Basic validation - alphanumeric, hyphens, underscores
324
+ if not re.match(r"^[a-zA-Z0-9_-]+$", v):
325
+ raise ValueError(f"Invalid project name: {v}")
326
+
327
+ return v.strip()
328
+
329
+ @field_validator("version")
330
+ @classmethod
331
+ def validate_version(cls, v: str | None) -> str | None:
332
+ """Validate semantic version string."""
333
+ if v is None:
334
+ return None
335
+
336
+ # Validate it's a valid semantic version
337
+ try:
338
+ SemanticVersion.from_string(v)
339
+ except ValueError as e:
340
+ raise ValueError(f"Invalid version format: {e}")
341
+
342
+ return v
343
+
344
+ @field_validator("steps")
345
+ @classmethod
346
+ def validate_steps(cls, v: list[DeploymentStep]) -> list[DeploymentStep]:
347
+ """Validate deployment steps."""
348
+ if not v:
349
+ raise ValueError("Must specify at least one deployment step")
350
+
351
+ # Check for duplicate steps
352
+ if len(v) != len(set(v)):
353
+ raise ValueError("Duplicate deployment steps found")
354
+
355
+ return v
356
+
357
+ @model_validator(mode="after")
358
+ def validate_config_consistency(self) -> "DeploymentConfig":
359
+ """Validate configuration consistency."""
360
+ # If PyPI publish is enabled, ensure we have build step first
361
+ if DeploymentStep.PYPI_PUBLISH in self.steps:
362
+ if DeploymentStep.BUILD not in self.steps:
363
+ raise ValueError("PyPI publish requires build step")
364
+
365
+ # Build must come before publish
366
+ build_idx = self.steps.index(DeploymentStep.BUILD)
367
+ publish_idx = self.steps.index(DeploymentStep.PYPI_PUBLISH)
368
+ if build_idx > publish_idx:
369
+ raise ValueError("Build step must come before PyPI publish")
370
+
371
+ # If local install is enabled, must have either build or publish
372
+ if DeploymentStep.LOCAL_INSTALL in self.steps:
373
+ if (
374
+ DeploymentStep.BUILD not in self.steps
375
+ and DeploymentStep.PYPI_PUBLISH not in self.steps
376
+ ):
377
+ raise ValueError("Local install requires build or PyPI publish step")
378
+
379
+ return self
380
+
381
+ @classmethod
382
+ def from_toml(cls, config_path: Path) -> "DeploymentConfig":
383
+ """Load configuration from TOML file.
384
+
385
+ Args:
386
+ config_path: Path to deployment config TOML file
387
+
388
+ Returns:
389
+ Validated DeploymentConfig instance
390
+
391
+ Raises:
392
+ FileNotFoundError: If config file doesn't exist
393
+ ValueError: If config is invalid
394
+ """
395
+ if not config_path.exists():
396
+ raise FileNotFoundError(f"Config file not found: {config_path}")
397
+
398
+ try:
399
+ import tomllib
400
+ except ImportError:
401
+ import tomli as tomllib # fallback for Python < 3.11
402
+
403
+ with open(config_path, "rb") as f:
404
+ data = tomllib.load(f)
405
+
406
+ # Extract sections
407
+ project = data.get("project", {})
408
+ deployment = data.get("deployment", {})
409
+
410
+ # Parse plugins
411
+ plugins = []
412
+ for name, cmd in deployment.get("plugins", {}).items():
413
+ plugins.append(PluginConfig(name=name, command=cmd))
414
+
415
+ # Build config
416
+ return cls(
417
+ project_name=project.get("name", "my-project"),
418
+ pypi_package=project.get("pypi_package"),
419
+ version=project.get("version"),
420
+ steps=[DeploymentStep(s) for s in deployment.get("steps", [])],
421
+ git=GitConfig(**deployment.get("git", {})),
422
+ build=BuildConfig(**deployment.get("build", {})),
423
+ pypi=PyPIConfig(**deployment.get("pypi", {})),
424
+ plugins=plugins,
425
+ hooks=DeploymentHooks(**deployment.get("hooks", {})),
426
+ )
427
+
428
+ def to_toml_dict(self) -> dict[str, Any]:
429
+ """Export configuration as TOML-compatible dict."""
430
+ return {
431
+ "project": {
432
+ "name": self.project_name,
433
+ "pypi_package": self.pypi_package,
434
+ "version": self.version,
435
+ },
436
+ "deployment": {
437
+ "steps": [step.value for step in self.steps],
438
+ "git": self.git.model_dump(),
439
+ "build": self.build.model_dump(),
440
+ "pypi": self.pypi.model_dump(),
441
+ "plugins": {p.name: p.command for p in self.plugins},
442
+ "hooks": self.hooks.model_dump(),
443
+ },
444
+ }
445
+
446
+
447
+ class DeploymentResult(BaseModel):
448
+ """Result of a deployment operation."""
449
+
450
+ success: bool = Field(..., description="Whether deployment succeeded")
451
+ version: str = Field(..., description="Version that was deployed")
452
+ steps_completed: list[DeploymentStep] = Field(
453
+ default_factory=list, description="Steps that completed"
454
+ )
455
+ steps_failed: list[DeploymentStep] = Field(
456
+ default_factory=list, description="Steps that failed"
457
+ )
458
+ errors: list[str] = Field(default_factory=list, description="Error messages")
459
+ duration_seconds: float = Field(
460
+ default=0.0, ge=0, description="Total deployment duration"
461
+ )
462
+
463
+ @property
464
+ def is_partial(self) -> bool:
465
+ """Check if deployment was only partially successful."""
466
+ return self.success and bool(self.steps_failed)
467
+
468
+ @property
469
+ def completion_rate(self) -> float:
470
+ """Calculate completion rate (0.0 to 1.0)."""
471
+ total_steps = len(self.steps_completed) + len(self.steps_failed)
472
+ if total_steps == 0:
473
+ return 0.0
474
+ return len(self.steps_completed) / total_steps