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,47 @@
1
+ {# Example user override template - copy to .htmlgraph/docs/templates/agents.md.j2 #}
2
+ {% extends "base_agents.md.j2" %}
3
+
4
+ {# Override the header to customize branding #}
5
+ {% block header %}
6
+ # 🤖 {{ platform|title }} Agent - Our Team Documentation
7
+ {% endblock %}
8
+
9
+ {# Add custom team workflows #}
10
+ {% block custom_workflows %}
11
+ ## Our Team Conventions
12
+
13
+ ### Daily Workflow
14
+ 1. **Morning Standup** - Review `sdk.summary()` for overnight progress
15
+ 2. **Pick Task** - Use `sdk.analytics.recommend_next_work()` for priorities
16
+ 3. **Daily Feature** - Create with template: `feat-{YYYYMMDD}-{description}`
17
+ 4. **End of Day** - Commit with `./scripts/git-commit-push.sh`
18
+
19
+ ### Commit Message Format
20
+ ```
21
+ type(scope): description
22
+
23
+ Examples:
24
+ - feat(auth): add OAuth provider integration
25
+ - fix(api): resolve session timeout bug
26
+ - docs(readme): update installation steps
27
+ ```
28
+
29
+ ### Branch Strategy
30
+ - `main` - Production-ready code
31
+ - `feature/*` - New features
32
+ - `fix/*` - Bug fixes
33
+ - `spike/*` - Research and experimentation
34
+
35
+ ### Code Review Checklist
36
+ - [ ] All tests pass
37
+ - [ ] Documentation updated
38
+ - [ ] No sensitive data in code
39
+ - [ ] Ruff and mypy pass
40
+ - [ ] Feature tracked in HtmlGraph
41
+ {% endblock %}
42
+
43
+ {# You can also override other blocks:
44
+ - introduction: Project-specific intro
45
+ - deployment: Team-specific deployment process
46
+ - footer: Custom footer with team info
47
+ #}
@@ -0,0 +1,163 @@
1
+ """
2
+ Version checking and interactive upgrade workflows.
3
+ """
4
+
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING
7
+
8
+ from rich.prompt import Prompt
9
+
10
+ from htmlgraph.docs.docs_version import get_current_doc_version, is_compatible
11
+ from htmlgraph.docs.metadata import DocsMetadata
12
+ from htmlgraph.docs.migrations import get_migration
13
+
14
+ if TYPE_CHECKING:
15
+ from htmlgraph.docs.migrations import MigrationScript
16
+
17
+
18
+ def check_docs_version(htmlgraph_dir: Path) -> tuple[bool, str | None]:
19
+ """Check if docs version is compatible.
20
+
21
+ Args:
22
+ htmlgraph_dir: Path to .htmlgraph directory
23
+
24
+ Returns:
25
+ Tuple of (is_compatible, message)
26
+ - is_compatible: True if compatible
27
+ - message: Optional warning/error message
28
+ """
29
+ metadata = DocsMetadata.load(htmlgraph_dir)
30
+ current_version = get_current_doc_version()
31
+
32
+ if metadata.schema_version == current_version:
33
+ return True, None
34
+
35
+ if is_compatible(metadata.schema_version, current_version):
36
+ return (
37
+ True,
38
+ f"⚠️ Docs version {metadata.schema_version} is supported but outdated (current: {current_version})",
39
+ )
40
+
41
+ return (
42
+ False,
43
+ f"❌ Docs version {metadata.schema_version} is incompatible with package (requires: {current_version})",
44
+ )
45
+
46
+
47
+ def upgrade_docs_interactive(htmlgraph_dir: Path) -> None:
48
+ """Interactive upgrade workflow with user prompts.
49
+
50
+ Args:
51
+ htmlgraph_dir: Path to .htmlgraph directory
52
+ """
53
+ metadata = DocsMetadata.load(htmlgraph_dir)
54
+ current_version = get_current_doc_version()
55
+
56
+ if metadata.schema_version == current_version:
57
+ print("✅ Docs are up to date")
58
+ return
59
+
60
+ # Get migration script
61
+ migration = get_migration(metadata.schema_version, current_version)
62
+ if not migration:
63
+ print(
64
+ f"❌ No migration available from v{metadata.schema_version} to v{current_version}"
65
+ )
66
+ return
67
+
68
+ # Show user their options
69
+ print(
70
+ f"""
71
+ 📋 Documentation Upgrade Available
72
+ Current: v{metadata.schema_version}
73
+ Target: v{current_version}
74
+
75
+ Options:
76
+ 1. Auto-migrate (preserves customizations)
77
+ 2. Side-by-side (test before committing)
78
+ 3. Manual migration (view diff first)
79
+ 4. Skip (stay on v{metadata.schema_version})
80
+ """
81
+ )
82
+
83
+ choice = Prompt.ask("Choose option", choices=["1", "2", "3", "4"], default="4")
84
+
85
+ if choice == "1":
86
+ _auto_migrate(htmlgraph_dir, migration)
87
+ elif choice == "2":
88
+ _side_by_side_migrate(htmlgraph_dir, migration)
89
+ elif choice == "3":
90
+ _show_diff_for_manual(htmlgraph_dir, migration)
91
+ else:
92
+ print("⏭️ Skipping migration")
93
+
94
+
95
+ def _auto_migrate(htmlgraph_dir: Path, migration: "MigrationScript") -> None: # type: ignore[name-defined]
96
+ """Automatically migrate with backup.
97
+
98
+ Args:
99
+ htmlgraph_dir: Path to .htmlgraph directory
100
+ migration: MigrationScript instance
101
+ """
102
+ backup_dir = htmlgraph_dir / ".docs-backups"
103
+ backup_dir.mkdir(exist_ok=True)
104
+
105
+ print("🚀 Starting auto-migration...")
106
+ success = migration.migrate(htmlgraph_dir, backup_dir)
107
+
108
+ if success:
109
+ print("✅ Migration complete!")
110
+ print(f"📦 Backup saved to {backup_dir}")
111
+ else:
112
+ print("❌ Migration failed. Docs unchanged.")
113
+
114
+
115
+ def _side_by_side_migrate(htmlgraph_dir: Path, migration: "MigrationScript") -> None: # type: ignore[name-defined]
116
+ """Create side-by-side versions for testing.
117
+
118
+ Args:
119
+ htmlgraph_dir: Path to .htmlgraph directory
120
+ migration: MigrationScript instance
121
+ """
122
+ print("📋 Creating side-by-side versions...")
123
+ print("⚠️ Side-by-side migration not yet implemented")
124
+ print(" Use option 1 (auto-migrate) or 3 (manual) instead")
125
+
126
+
127
+ def _show_diff_for_manual(htmlgraph_dir: Path, migration: "MigrationScript") -> None: # type: ignore[name-defined]
128
+ """Show diff preview for manual migration.
129
+
130
+ Args:
131
+ htmlgraph_dir: Path to .htmlgraph directory
132
+ migration: MigrationScript instance
133
+ """
134
+ print("📊 Showing migration preview...")
135
+ print("⚠️ Diff preview not yet implemented")
136
+ print(" Use option 1 (auto-migrate) instead")
137
+
138
+
139
+ def check_version_on_init(htmlgraph_dir: Path, auto_upgrade: bool = False) -> bool:
140
+ """Check version compatibility on SDK initialization.
141
+
142
+ Args:
143
+ htmlgraph_dir: Path to .htmlgraph directory
144
+ auto_upgrade: If True, automatically upgrade if safe
145
+
146
+ Returns:
147
+ True if compatible or upgraded successfully
148
+ """
149
+ compatible, message = check_docs_version(htmlgraph_dir)
150
+
151
+ if compatible and message:
152
+ # Compatible but outdated
153
+ print(message)
154
+ if auto_upgrade:
155
+ upgrade_docs_interactive(htmlgraph_dir)
156
+ return True
157
+
158
+ if not compatible:
159
+ print(message)
160
+ print("\nRun `uv run htmlgraph docs upgrade` to migrate.")
161
+ return False
162
+
163
+ return True
htmlgraph/edge_index.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Edge Index for O(1) reverse edge lookups.
3
5
 
@@ -10,13 +12,14 @@ Without this index, finding incoming edges requires scanning all nodes
10
12
  in the graph - O(V×E) complexity.
11
13
  """
12
14
 
13
- from __future__ import annotations
15
+
14
16
  from collections import defaultdict
17
+ from collections.abc import Iterator
15
18
  from dataclasses import dataclass, field
16
- from typing import TYPE_CHECKING, Iterator
19
+ from typing import TYPE_CHECKING
17
20
 
18
21
  if TYPE_CHECKING:
19
- from htmlgraph.models import Node, Edge
22
+ from htmlgraph.models import Edge, Node
20
23
 
21
24
 
22
25
  @dataclass
@@ -27,14 +30,50 @@ class EdgeRef:
27
30
  Stores the essential information needed to identify and traverse
28
31
  an edge without holding a reference to the full Edge object.
29
32
  """
33
+
30
34
  source_id: str
31
35
  target_id: str
32
36
  relationship: str
33
37
 
34
38
  def __hash__(self) -> int:
39
+ """
40
+ Compute hash for EdgeRef.
41
+
42
+ Enables using EdgeRef in sets and as dict keys.
43
+
44
+ Returns:
45
+ int: Hash value based on source_id, target_id, and relationship
46
+
47
+ Example:
48
+ >>> ref1 = EdgeRef("feat-001", "feat-002", "blocked_by")
49
+ >>> ref2 = EdgeRef("feat-001", "feat-002", "blocked_by")
50
+ >>> refs = {ref1, ref2} # Set deduplication works
51
+ >>> len(refs)
52
+ 1
53
+ """
35
54
  return hash((self.source_id, self.target_id, self.relationship))
36
55
 
37
56
  def __eq__(self, other: object) -> bool:
57
+ """
58
+ Check equality with another EdgeRef.
59
+
60
+ Enables using == operator and set membership checks.
61
+
62
+ Args:
63
+ other: Object to compare with
64
+
65
+ Returns:
66
+ bool: True if both EdgeRefs have same source, target, and relationship
67
+
68
+ Example:
69
+ >>> ref1 = EdgeRef("feat-001", "feat-002", "blocked_by")
70
+ >>> ref2 = EdgeRef("feat-001", "feat-002", "blocked_by")
71
+ >>> ref1 == ref2
72
+ True
73
+ >>> ref3 = EdgeRef("feat-001", "feat-003", "blocked_by")
74
+ >>> ref1 == ref3
75
+ False
76
+ """
38
77
  if not isinstance(other, EdgeRef):
39
78
  return False
40
79
  return (
@@ -67,8 +106,12 @@ class EdgeIndex:
67
106
  blocked = index.get_outgoing("feature-001", relationship="blocks")
68
107
  """
69
108
 
70
- _incoming: dict[str, list[EdgeRef]] = field(default_factory=lambda: defaultdict(list))
71
- _outgoing: dict[str, list[EdgeRef]] = field(default_factory=lambda: defaultdict(list))
109
+ _incoming: dict[str, list[EdgeRef]] = field(
110
+ default_factory=lambda: defaultdict(list)
111
+ )
112
+ _outgoing: dict[str, list[EdgeRef]] = field(
113
+ default_factory=lambda: defaultdict(list)
114
+ )
72
115
  _edge_count: int = 0
73
116
 
74
117
  def add(self, source_id: str, target_id: str, relationship: str) -> EdgeRef:
@@ -83,7 +126,9 @@ class EdgeIndex:
83
126
  Returns:
84
127
  EdgeRef for the added edge
85
128
  """
86
- ref = EdgeRef(source_id=source_id, target_id=target_id, relationship=relationship)
129
+ ref = EdgeRef(
130
+ source_id=source_id, target_id=target_id, relationship=relationship
131
+ )
87
132
 
88
133
  # Avoid duplicates
89
134
  if ref not in self._incoming[target_id]:
@@ -93,7 +138,7 @@ class EdgeIndex:
93
138
 
94
139
  return ref
95
140
 
96
- def add_edge(self, source_id: str, edge: 'Edge') -> EdgeRef:
141
+ def add_edge(self, source_id: str, edge: Edge) -> EdgeRef:
97
142
  """
98
143
  Add an edge object to the index.
99
144
 
@@ -118,7 +163,9 @@ class EdgeIndex:
118
163
  Returns:
119
164
  True if edge was removed, False if not found
120
165
  """
121
- ref = EdgeRef(source_id=source_id, target_id=target_id, relationship=relationship)
166
+ ref = EdgeRef(
167
+ source_id=source_id, target_id=target_id, relationship=relationship
168
+ )
122
169
 
123
170
  removed = False
124
171
  if target_id in self._incoming and ref in self._incoming[target_id]:
@@ -134,7 +181,7 @@ class EdgeIndex:
134
181
 
135
182
  return removed
136
183
 
137
- def remove_edge(self, source_id: str, edge: 'Edge') -> bool:
184
+ def remove_edge(self, source_id: str, edge: Edge) -> bool:
138
185
  """
139
186
  Remove an edge object from the index.
140
187
 
@@ -184,10 +231,88 @@ class EdgeIndex:
184
231
  self._edge_count -= removed
185
232
  return removed
186
233
 
234
+ def add_node_edges(self, node_id: str, node: Node) -> int:
235
+ """
236
+ Add a single node's outgoing edges to the index.
237
+
238
+ Args:
239
+ node_id: Node ID
240
+ node: Node object with edges to add
241
+
242
+ Returns:
243
+ Number of edges added
244
+ """
245
+ added = 0
246
+ for rel_type, edges in node.edges.items():
247
+ for edge in edges:
248
+ self.add(node_id, edge.target_id, rel_type)
249
+ added += 1
250
+ return added
251
+
252
+ def add_node(self, node_id: str, node: Node) -> int:
253
+ """
254
+ Add all edges from a single node to the index.
255
+
256
+ Alias for add_node_edges() to match requested API.
257
+
258
+ Args:
259
+ node_id: The node's ID
260
+ node: The node object with edges attribute
261
+
262
+ Returns:
263
+ Number of edges added
264
+ """
265
+ return self.add_node_edges(node_id, node)
266
+
267
+ def remove_node_edges(self, node_id: str, node: Node) -> int:
268
+ """
269
+ Remove a single node's outgoing edges from the index.
270
+
271
+ Args:
272
+ node_id: Node ID
273
+ node: Node object with edges to remove
274
+
275
+ Returns:
276
+ Number of edges removed
277
+ """
278
+ removed = 0
279
+ for rel_type, edges in node.edges.items():
280
+ for edge in edges:
281
+ if self.remove(node_id, edge.target_id, rel_type):
282
+ removed += 1
283
+ return removed
284
+
285
+ def update_node(
286
+ self, node_id: str, old_node: Node, new_node: Node
287
+ ) -> tuple[int, int]:
288
+ """
289
+ Update a node's edges atomically (remove old, add new).
290
+
291
+ This is an atomic operation that removes all edges from the old node
292
+ and adds all edges from the new node. Useful for updating a node
293
+ without leaving orphaned edges.
294
+
295
+ Args:
296
+ node_id: The node's ID
297
+ old_node: The previous node object
298
+ new_node: The updated node object
299
+
300
+ Returns:
301
+ Tuple of (removed_count, added_count)
302
+
303
+ Example:
304
+ >>> old = Node(id="feat-001", edges={"blocks": [Edge(target_id="feat-002")]})
305
+ >>> new = Node(id="feat-001", edges={"blocks": [Edge(target_id="feat-003")]})
306
+ >>> removed, added = index.update_node("feat-001", old, new)
307
+ >>> print(f"Removed {removed}, added {added}")
308
+ Removed 1, added 1
309
+ """
310
+ removed = self.remove_node_edges(node_id, old_node)
311
+ added = self.add_node_edges(node_id, new_node)
312
+ return (removed, added)
313
+
187
314
  def get_incoming(
188
- self,
189
- target_id: str,
190
- relationship: str | None = None
315
+ self, target_id: str, relationship: str | None = None
191
316
  ) -> list[EdgeRef]:
192
317
  """
193
318
  Get all edges pointing TO a node (O(1) lookup).
@@ -213,9 +338,7 @@ class EdgeIndex:
213
338
  return list(edges)
214
339
 
215
340
  def get_outgoing(
216
- self,
217
- source_id: str,
218
- relationship: str | None = None
341
+ self, source_id: str, relationship: str | None = None
219
342
  ) -> list[EdgeRef]:
220
343
  """
221
344
  Get all edges pointing FROM a node (O(1) lookup).
@@ -235,10 +358,7 @@ class EdgeIndex:
235
358
  return list(edges)
236
359
 
237
360
  def get_neighbors(
238
- self,
239
- node_id: str,
240
- relationship: str | None = None,
241
- direction: str = "both"
361
+ self, node_id: str, relationship: str | None = None, direction: str = "both"
242
362
  ) -> set[str]:
243
363
  """
244
364
  Get all neighboring node IDs connected to a node.
@@ -263,7 +383,9 @@ class EdgeIndex:
263
383
 
264
384
  return neighbors
265
385
 
266
- def has_edge(self, source_id: str, target_id: str, relationship: str | None = None) -> bool:
386
+ def has_edge(
387
+ self, source_id: str, target_id: str, relationship: str | None = None
388
+ ) -> bool:
267
389
  """
268
390
  Check if an edge exists between two nodes.
269
391
 
@@ -281,10 +403,12 @@ class EdgeIndex:
281
403
  return True
282
404
  return False
283
405
 
284
- def rebuild(self, nodes: dict[str, 'Node']) -> int:
406
+ def rebuild(self, nodes: dict[str, Node]) -> int:
285
407
  """
286
408
  Rebuild the entire index from a node dictionary.
287
409
 
410
+ Optimized to use add_node_edges() for cleaner code.
411
+
288
412
  Args:
289
413
  nodes: Dictionary mapping node_id to Node objects
290
414
 
@@ -294,9 +418,7 @@ class EdgeIndex:
294
418
  self.clear()
295
419
 
296
420
  for node_id, node in nodes.items():
297
- for relationship, edges in node.edges.items():
298
- for edge in edges:
299
- self.add(node_id, edge.target_id, edge.relationship)
421
+ self.add_node_edges(node_id, node)
300
422
 
301
423
  return self._edge_count
302
424
 
@@ -307,11 +429,44 @@ class EdgeIndex:
307
429
  self._edge_count = 0
308
430
 
309
431
  def __len__(self) -> int:
310
- """Return number of edges in the index."""
432
+ """
433
+ Get the total number of edges in the index.
434
+
435
+ Enables using len() on EdgeIndex instances.
436
+
437
+ Returns:
438
+ int: Total number of edges indexed
439
+
440
+ Example:
441
+ >>> index = EdgeIndex()
442
+ >>> index.rebuild(graph.nodes)
443
+ >>> print(f"Index contains {len(index)} edges")
444
+ Index contains 156 edges
445
+ """
311
446
  return self._edge_count
312
447
 
313
448
  def __iter__(self) -> Iterator[EdgeRef]:
314
- """Iterate over all edges in the index."""
449
+ """
450
+ Iterate over all unique edges in the index.
451
+
452
+ Enables using EdgeIndex in for loops and other iteration contexts.
453
+ Deduplicates edges to avoid returning the same edge twice.
454
+
455
+ Yields:
456
+ EdgeRef: Each unique edge in the index
457
+
458
+ Example:
459
+ >>> index = EdgeIndex()
460
+ >>> index.rebuild(graph.nodes)
461
+ >>> for edge in index:
462
+ ... print(f"{edge.source_id} --{edge.relationship}--> {edge.target_id}")
463
+ feat-001 --blocked_by--> feat-002
464
+ feat-003 --related--> feat-001
465
+
466
+ >>> # Works with comprehensions
467
+ >>> blocked_by = [e for e in index if e.relationship == "blocked_by"]
468
+ >>> blocking_count = len([e for e in index if e.relationship == "blocks"])
469
+ """
315
470
  seen: set[EdgeRef] = set()
316
471
  for refs in self._outgoing.values():
317
472
  for ref in refs:
@@ -330,5 +485,5 @@ class EdgeIndex:
330
485
  "edge_count": self._edge_count,
331
486
  "nodes_with_incoming": len(self._incoming),
332
487
  "nodes_with_outgoing": len(self._outgoing),
333
- "relationships": list(set(ref.relationship for ref in self))
488
+ "relationships": list(set(ref.relationship for ref in self)),
334
489
  }