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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +173 -17
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +127 -0
- htmlgraph/agent_registry.py +45 -30
- htmlgraph/agents.py +160 -107
- htmlgraph/analytics/__init__.py +9 -2
- htmlgraph/analytics/cli.py +190 -51
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +192 -100
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +190 -14
- htmlgraph/analytics_index.py +135 -51
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +208 -0
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/__init__.py +14 -0
- htmlgraph/builders/base.py +118 -29
- htmlgraph/builders/bug.py +150 -0
- htmlgraph/builders/chore.py +119 -0
- htmlgraph/builders/epic.py +150 -0
- htmlgraph/builders/feature.py +31 -6
- htmlgraph/builders/insight.py +195 -0
- htmlgraph/builders/metric.py +217 -0
- htmlgraph/builders/pattern.py +202 -0
- htmlgraph/builders/phase.py +162 -0
- htmlgraph/builders/spike.py +52 -19
- htmlgraph/builders/track.py +148 -72
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +18 -0
- htmlgraph/collections/base.py +415 -98
- htmlgraph/collections/bug.py +53 -0
- htmlgraph/collections/chore.py +53 -0
- htmlgraph/collections/epic.py +53 -0
- htmlgraph/collections/feature.py +12 -26
- htmlgraph/collections/insight.py +100 -0
- htmlgraph/collections/metric.py +92 -0
- htmlgraph/collections/pattern.py +97 -0
- htmlgraph/collections/phase.py +53 -0
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +56 -16
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +511 -0
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +344 -0
- htmlgraph/converter.py +216 -28
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2406 -307
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +19 -2
- htmlgraph/deploy.py +142 -125
- htmlgraph/deployment_models.py +474 -0
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +182 -27
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +100 -52
- htmlgraph/event_migration.py +13 -4
- htmlgraph/exceptions.py +49 -0
- htmlgraph/file_watcher.py +101 -28
- htmlgraph/find_api.py +75 -63
- htmlgraph/git_events.py +145 -63
- htmlgraph/graph.py +1122 -106
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +45 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +1314 -0
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/hooks-config.example.json +12 -0
- htmlgraph/hooks/installer.py +343 -0
- htmlgraph/hooks/orchestrator.py +674 -0
- htmlgraph/hooks/orchestrator_reflector.py +223 -0
- htmlgraph/hooks/post-checkout.sh +28 -0
- htmlgraph/hooks/post-commit.sh +24 -0
- htmlgraph/hooks/post-merge.sh +26 -0
- htmlgraph/hooks/post_tool_use_failure.py +273 -0
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +408 -0
- htmlgraph/hooks/pre-commit.sh +94 -0
- htmlgraph/hooks/pre-push.sh +28 -0
- htmlgraph/hooks/pretooluse.py +819 -0
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +255 -0
- htmlgraph/hooks/task_validator.py +177 -0
- htmlgraph/hooks/validator.py +628 -0
- htmlgraph/ids.py +41 -27
- htmlgraph/index.d.ts +286 -0
- htmlgraph/learning.py +767 -0
- htmlgraph/mcp_server.py +69 -23
- htmlgraph/models.py +1586 -87
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/orchestration/task_coordination.py +343 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +669 -0
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +328 -0
- htmlgraph/orchestrator_validator.py +133 -0
- htmlgraph/parallel.py +646 -0
- htmlgraph/parser.py +160 -35
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/planning.py +147 -52
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +109 -72
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/routing.py +8 -19
- htmlgraph/scripts/deploy.py +1 -2
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +685 -180
- htmlgraph/services/__init__.py +10 -0
- htmlgraph/services/claiming.py +199 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +1392 -175
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +201 -0
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/setup.py +34 -17
- htmlgraph/spike_index.py +143 -0
- htmlgraph/sync_docs.py +12 -15
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph/templates/GEMINI.md.template +87 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +146 -15
- htmlgraph/track_manager.py +69 -21
- htmlgraph/transcript.py +890 -0
- htmlgraph/transcript_analytics.py +699 -0
- htmlgraph/types.py +323 -0
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +8 -5
- htmlgraph/work_type_utils.py +3 -2
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
- htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -2688
- htmlgraph/sdk.py +0 -709
- htmlgraph-0.9.3.dist-info/RECORD +0 -61
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {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
|
-
|
|
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
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
17
20
|
|
|
18
21
|
if TYPE_CHECKING:
|
|
19
|
-
from htmlgraph.models import
|
|
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(
|
|
71
|
-
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
}
|