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,289 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""HtmlGraph bootstrap operations.
|
|
4
|
+
|
|
5
|
+
One-command setup to go from installation to first value in under 60 seconds.
|
|
6
|
+
This module provides functions for bootstrapping a project with HtmlGraph.
|
|
7
|
+
|
|
8
|
+
The bootstrap process includes:
|
|
9
|
+
1. Auto-detecting project type (Python, Node, etc.)
|
|
10
|
+
2. Creating .htmlgraph directory structure
|
|
11
|
+
3. Initializing database with schema
|
|
12
|
+
4. Installing Claude Code plugin hooks automatically
|
|
13
|
+
5. Printing next steps for the user
|
|
14
|
+
|
|
15
|
+
This is designed for simplicity and speed - the minimal viable setup.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import subprocess
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import TYPE_CHECKING, Any
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from htmlgraph.cli.models import BootstrapConfig
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def detect_project_type(project_dir: Path) -> str:
|
|
29
|
+
"""
|
|
30
|
+
Auto-detect project type from files in directory.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
project_dir: Project directory to inspect
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Detected project type: "python", "node", "multi", or "unknown"
|
|
37
|
+
"""
|
|
38
|
+
# Check for Python project markers
|
|
39
|
+
has_python = any(
|
|
40
|
+
[
|
|
41
|
+
(project_dir / "pyproject.toml").exists(),
|
|
42
|
+
(project_dir / "setup.py").exists(),
|
|
43
|
+
(project_dir / "requirements.txt").exists(),
|
|
44
|
+
(project_dir / "Pipfile").exists(),
|
|
45
|
+
]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Check for Node project markers
|
|
49
|
+
has_node = (project_dir / "package.json").exists()
|
|
50
|
+
|
|
51
|
+
# Determine project type
|
|
52
|
+
if has_python and has_node:
|
|
53
|
+
return "multi"
|
|
54
|
+
elif has_python:
|
|
55
|
+
return "python"
|
|
56
|
+
elif has_node:
|
|
57
|
+
return "node"
|
|
58
|
+
else:
|
|
59
|
+
return "unknown"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def create_gitignore_template() -> str:
|
|
63
|
+
"""
|
|
64
|
+
Create .gitignore template content for .htmlgraph directory.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Gitignore template content
|
|
68
|
+
"""
|
|
69
|
+
return """# HtmlGraph cache and regenerable files
|
|
70
|
+
.htmlgraph/htmlgraph.db
|
|
71
|
+
.htmlgraph/sessions/*.jsonl
|
|
72
|
+
.htmlgraph/events/*.jsonl
|
|
73
|
+
.htmlgraph/parent-activity.json
|
|
74
|
+
.htmlgraph/logs/
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def check_already_initialized(project_dir: Path) -> bool:
|
|
79
|
+
"""
|
|
80
|
+
Check if project is already initialized with HtmlGraph.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
project_dir: Project directory to check
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if already initialized, False otherwise
|
|
87
|
+
"""
|
|
88
|
+
graph_dir = project_dir / ".htmlgraph"
|
|
89
|
+
return graph_dir.exists()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def create_bootstrap_structure(project_dir: Path) -> dict[str, list[str]]:
|
|
93
|
+
"""
|
|
94
|
+
Create minimal .htmlgraph directory structure for bootstrap.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
project_dir: Project directory
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Dictionary with lists of created directories and files
|
|
101
|
+
"""
|
|
102
|
+
graph_dir = project_dir / ".htmlgraph"
|
|
103
|
+
created_dirs: list[str] = []
|
|
104
|
+
created_files: list[str] = []
|
|
105
|
+
|
|
106
|
+
# Create main .htmlgraph directory
|
|
107
|
+
if not graph_dir.exists():
|
|
108
|
+
graph_dir.mkdir(parents=True)
|
|
109
|
+
created_dirs.append(str(graph_dir))
|
|
110
|
+
|
|
111
|
+
# Create subdirectories
|
|
112
|
+
subdirs = [
|
|
113
|
+
"sessions",
|
|
114
|
+
"features",
|
|
115
|
+
"spikes",
|
|
116
|
+
"tracks",
|
|
117
|
+
"events",
|
|
118
|
+
"logs",
|
|
119
|
+
"logs/errors",
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
for subdir in subdirs:
|
|
123
|
+
subdir_path = graph_dir / subdir
|
|
124
|
+
if not subdir_path.exists():
|
|
125
|
+
subdir_path.mkdir(parents=True)
|
|
126
|
+
created_dirs.append(str(subdir_path))
|
|
127
|
+
|
|
128
|
+
# Create .gitignore in .htmlgraph
|
|
129
|
+
gitignore = graph_dir / ".gitignore"
|
|
130
|
+
if not gitignore.exists():
|
|
131
|
+
gitignore.write_text(create_gitignore_template())
|
|
132
|
+
created_files.append(str(gitignore))
|
|
133
|
+
|
|
134
|
+
# Create config.json
|
|
135
|
+
config_file = graph_dir / "config.json"
|
|
136
|
+
if not config_file.exists():
|
|
137
|
+
config_data = {
|
|
138
|
+
"bootstrapped": True,
|
|
139
|
+
"version": "1.0",
|
|
140
|
+
}
|
|
141
|
+
config_file.write_text(json.dumps(config_data, indent=2) + "\n")
|
|
142
|
+
created_files.append(str(config_file))
|
|
143
|
+
|
|
144
|
+
return {"directories": created_dirs, "files": created_files}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def initialize_database(graph_dir: Path) -> str:
|
|
148
|
+
"""
|
|
149
|
+
Initialize HtmlGraph database with schema.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
graph_dir: Path to .htmlgraph directory
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Path to created database file
|
|
156
|
+
"""
|
|
157
|
+
from htmlgraph.db.schema import HtmlGraphDB
|
|
158
|
+
|
|
159
|
+
db_path = graph_dir / "htmlgraph.db"
|
|
160
|
+
|
|
161
|
+
# Create database using HtmlGraphDB (auto-creates tables)
|
|
162
|
+
db = HtmlGraphDB(db_path=str(db_path))
|
|
163
|
+
db.disconnect()
|
|
164
|
+
|
|
165
|
+
return str(db_path)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def check_claude_code_available() -> bool:
|
|
169
|
+
"""
|
|
170
|
+
Check if Claude Code CLI is available.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
True if claude command is available, False otherwise
|
|
174
|
+
"""
|
|
175
|
+
try:
|
|
176
|
+
result = subprocess.run(
|
|
177
|
+
["claude", "--version"],
|
|
178
|
+
capture_output=True,
|
|
179
|
+
check=False,
|
|
180
|
+
timeout=5,
|
|
181
|
+
)
|
|
182
|
+
return result.returncode == 0
|
|
183
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def get_next_steps(
|
|
188
|
+
project_type: str, has_claude: bool, plugin_installed: bool
|
|
189
|
+
) -> list[str]:
|
|
190
|
+
"""
|
|
191
|
+
Generate next steps message based on project state.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
project_type: Detected project type
|
|
195
|
+
has_claude: Whether Claude Code CLI is available
|
|
196
|
+
plugin_installed: Whether plugin hooks were installed
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List of next step messages
|
|
200
|
+
"""
|
|
201
|
+
steps = []
|
|
202
|
+
|
|
203
|
+
if has_claude:
|
|
204
|
+
if plugin_installed:
|
|
205
|
+
steps.append("1. Use Claude Code: Run 'claude --dev' in this project")
|
|
206
|
+
else:
|
|
207
|
+
steps.append(
|
|
208
|
+
"1. Install HtmlGraph plugin: Run 'claude plugin install htmlgraph'"
|
|
209
|
+
)
|
|
210
|
+
steps.append("2. Use Claude Code: Run 'claude --dev' in this project")
|
|
211
|
+
else:
|
|
212
|
+
steps.append(
|
|
213
|
+
"1. Install Claude Code CLI: Visit https://code.claude.com/docs/installation"
|
|
214
|
+
)
|
|
215
|
+
steps.append(
|
|
216
|
+
"2. Install HtmlGraph plugin: Run 'claude plugin install htmlgraph'"
|
|
217
|
+
)
|
|
218
|
+
steps.append("3. Use Claude Code: Run 'claude --dev' in this project")
|
|
219
|
+
|
|
220
|
+
steps.append(
|
|
221
|
+
f"{len(steps) + 1}. Track work: Create features with 'htmlgraph feature create \"Title\"'"
|
|
222
|
+
)
|
|
223
|
+
steps.append(f"{len(steps) + 1}. View progress: Run 'htmlgraph status'")
|
|
224
|
+
steps.append(
|
|
225
|
+
f"{len(steps) + 1}. See what Claude did: Run 'htmlgraph serve' and open http://localhost:8080"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return steps
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def bootstrap_htmlgraph(config: BootstrapConfig) -> dict[str, Any]:
|
|
232
|
+
"""
|
|
233
|
+
Bootstrap HtmlGraph in a project directory.
|
|
234
|
+
|
|
235
|
+
This is the main entry point for the bootstrap command.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
config: BootstrapConfig with bootstrap settings
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Dictionary with bootstrap results
|
|
242
|
+
"""
|
|
243
|
+
project_dir = Path(config.project_path).resolve()
|
|
244
|
+
|
|
245
|
+
# Check if already initialized
|
|
246
|
+
if check_already_initialized(project_dir):
|
|
247
|
+
# Ask user if they want to overwrite
|
|
248
|
+
print(f"\n⚠️ HtmlGraph already initialized in {project_dir}")
|
|
249
|
+
response = input("Do you want to reinitialize? (y/N): ").strip().lower()
|
|
250
|
+
if response not in ["y", "yes"]:
|
|
251
|
+
return {
|
|
252
|
+
"success": False,
|
|
253
|
+
"message": "Bootstrap cancelled - already initialized",
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
# Detect project type
|
|
257
|
+
project_type = detect_project_type(project_dir)
|
|
258
|
+
|
|
259
|
+
# Create directory structure
|
|
260
|
+
created = create_bootstrap_structure(project_dir)
|
|
261
|
+
graph_dir = project_dir / ".htmlgraph"
|
|
262
|
+
|
|
263
|
+
# Initialize database
|
|
264
|
+
db_path = initialize_database(graph_dir)
|
|
265
|
+
created["files"].append(db_path)
|
|
266
|
+
|
|
267
|
+
# Check for Claude Code
|
|
268
|
+
has_claude = check_claude_code_available()
|
|
269
|
+
|
|
270
|
+
# Check if plugin is already available (skip installation check for now)
|
|
271
|
+
plugin_installed = False
|
|
272
|
+
if not config.no_plugins and has_claude:
|
|
273
|
+
# We'll consider it "installed" if hooks can be configured
|
|
274
|
+
# The actual plugin installation happens via marketplace
|
|
275
|
+
plugin_installed = True
|
|
276
|
+
|
|
277
|
+
# Generate next steps
|
|
278
|
+
next_steps = get_next_steps(project_type, has_claude, plugin_installed)
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
"success": True,
|
|
282
|
+
"project_type": project_type,
|
|
283
|
+
"graph_dir": str(graph_dir),
|
|
284
|
+
"directories_created": created["directories"],
|
|
285
|
+
"files_created": created["files"],
|
|
286
|
+
"has_claude": has_claude,
|
|
287
|
+
"plugin_installed": plugin_installed,
|
|
288
|
+
"next_steps": next_steps,
|
|
289
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Event and analytics index operations for HtmlGraph."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class EventRebuildResult:
|
|
13
|
+
"""Result of rebuilding the event index."""
|
|
14
|
+
|
|
15
|
+
db_path: Path
|
|
16
|
+
inserted: int
|
|
17
|
+
skipped: int
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class EventStats:
|
|
22
|
+
"""Statistics about events in the system."""
|
|
23
|
+
|
|
24
|
+
total_events: int
|
|
25
|
+
session_count: int
|
|
26
|
+
file_count: int
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class EventQueryResult:
|
|
31
|
+
"""Result of querying events."""
|
|
32
|
+
|
|
33
|
+
events: list[dict[str, Any]]
|
|
34
|
+
total: int
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class EventExportResult:
|
|
39
|
+
"""Result of exporting sessions to JSONL."""
|
|
40
|
+
|
|
41
|
+
written: int
|
|
42
|
+
skipped: int
|
|
43
|
+
failed: int
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class EventOperationError(RuntimeError):
|
|
47
|
+
"""Base error for event operations."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def export_sessions(*, graph_dir: Path, overwrite: bool = False) -> EventExportResult:
|
|
51
|
+
"""
|
|
52
|
+
Export legacy session HTML logs to JSONL events.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
graph_dir: Path to .htmlgraph directory
|
|
56
|
+
overwrite: Whether to overwrite existing JSONL files
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
EventExportResult with counts of written, skipped, failed files
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
EventOperationError: If graph_dir doesn't exist or isn't a directory
|
|
63
|
+
"""
|
|
64
|
+
if not graph_dir.exists():
|
|
65
|
+
raise EventOperationError(f"Graph directory not found: {graph_dir}")
|
|
66
|
+
if not graph_dir.is_dir():
|
|
67
|
+
raise EventOperationError(f"Not a directory: {graph_dir}")
|
|
68
|
+
|
|
69
|
+
from htmlgraph.event_migration import export_sessions_to_jsonl
|
|
70
|
+
|
|
71
|
+
sessions_dir = graph_dir / "sessions"
|
|
72
|
+
events_dir = graph_dir / "events"
|
|
73
|
+
|
|
74
|
+
if not sessions_dir.exists():
|
|
75
|
+
raise EventOperationError(f"Sessions directory not found: {sessions_dir}")
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
result = export_sessions_to_jsonl(
|
|
79
|
+
sessions_dir=sessions_dir,
|
|
80
|
+
events_dir=events_dir,
|
|
81
|
+
overwrite=overwrite,
|
|
82
|
+
include_subdirs=False,
|
|
83
|
+
)
|
|
84
|
+
return EventExportResult(
|
|
85
|
+
written=result["written"],
|
|
86
|
+
skipped=result["skipped"],
|
|
87
|
+
failed=result["failed"],
|
|
88
|
+
)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
raise EventOperationError(f"Failed to export sessions: {e}") from e
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def rebuild_index(*, graph_dir: Path) -> EventRebuildResult:
|
|
94
|
+
"""
|
|
95
|
+
Rebuild the SQLite analytics index from JSONL events.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
graph_dir: Path to .htmlgraph directory
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
EventRebuildResult with db_path and counts of inserted/skipped events
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
EventOperationError: If events directory doesn't exist or rebuild fails
|
|
105
|
+
"""
|
|
106
|
+
if not graph_dir.exists():
|
|
107
|
+
raise EventOperationError(f"Graph directory not found: {graph_dir}")
|
|
108
|
+
if not graph_dir.is_dir():
|
|
109
|
+
raise EventOperationError(f"Not a directory: {graph_dir}")
|
|
110
|
+
|
|
111
|
+
from htmlgraph.analytics_index import AnalyticsIndex
|
|
112
|
+
from htmlgraph.event_log import JsonlEventLog
|
|
113
|
+
|
|
114
|
+
events_dir = graph_dir / "events"
|
|
115
|
+
db_path = graph_dir / "index.sqlite"
|
|
116
|
+
|
|
117
|
+
if not events_dir.exists():
|
|
118
|
+
raise EventOperationError(f"Events directory not found: {events_dir}")
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
log = JsonlEventLog(events_dir)
|
|
122
|
+
index = AnalyticsIndex(db_path)
|
|
123
|
+
|
|
124
|
+
# Stream events from all JSONL files
|
|
125
|
+
events = (event for _, event in log.iter_events())
|
|
126
|
+
result = index.rebuild_from_events(events)
|
|
127
|
+
|
|
128
|
+
return EventRebuildResult(
|
|
129
|
+
db_path=db_path,
|
|
130
|
+
inserted=result["inserted"],
|
|
131
|
+
skipped=result["skipped"],
|
|
132
|
+
)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
raise EventOperationError(f"Failed to rebuild index: {e}") from e
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def query_events(
|
|
138
|
+
*,
|
|
139
|
+
graph_dir: Path,
|
|
140
|
+
session_id: str | None = None,
|
|
141
|
+
tool: str | None = None,
|
|
142
|
+
feature_id: str | None = None,
|
|
143
|
+
since: str | None = None,
|
|
144
|
+
limit: int | None = None,
|
|
145
|
+
) -> EventQueryResult:
|
|
146
|
+
"""
|
|
147
|
+
Query events from JSONL logs with optional filters.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
graph_dir: Path to .htmlgraph directory
|
|
151
|
+
session_id: Filter by session ID (None = all sessions)
|
|
152
|
+
tool: Filter by tool name (e.g., 'Bash', 'Edit')
|
|
153
|
+
feature_id: Filter by attributed feature ID
|
|
154
|
+
since: Only events after this timestamp (ISO string)
|
|
155
|
+
limit: Maximum number of events to return
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
EventQueryResult with matching events and total count
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
EventOperationError: If events directory doesn't exist or query fails
|
|
162
|
+
"""
|
|
163
|
+
if not graph_dir.exists():
|
|
164
|
+
raise EventOperationError(f"Graph directory not found: {graph_dir}")
|
|
165
|
+
if not graph_dir.is_dir():
|
|
166
|
+
raise EventOperationError(f"Not a directory: {graph_dir}")
|
|
167
|
+
|
|
168
|
+
from htmlgraph.event_log import JsonlEventLog
|
|
169
|
+
|
|
170
|
+
events_dir = graph_dir / "events"
|
|
171
|
+
|
|
172
|
+
if not events_dir.exists():
|
|
173
|
+
raise EventOperationError(f"Events directory not found: {events_dir}")
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
log = JsonlEventLog(events_dir)
|
|
177
|
+
events = log.query_events(
|
|
178
|
+
session_id=session_id,
|
|
179
|
+
tool=tool,
|
|
180
|
+
feature_id=feature_id,
|
|
181
|
+
since=since,
|
|
182
|
+
limit=limit,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return EventQueryResult(
|
|
186
|
+
events=events,
|
|
187
|
+
total=len(events),
|
|
188
|
+
)
|
|
189
|
+
except Exception as e:
|
|
190
|
+
raise EventOperationError(f"Failed to query events: {e}") from e
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def get_event_stats(*, graph_dir: Path) -> EventStats:
|
|
194
|
+
"""
|
|
195
|
+
Get statistics about events in the system.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
graph_dir: Path to .htmlgraph directory
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
EventStats with counts of total events, sessions, and files
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
EventOperationError: If events directory doesn't exist or stats collection fails
|
|
205
|
+
"""
|
|
206
|
+
if not graph_dir.exists():
|
|
207
|
+
raise EventOperationError(f"Graph directory not found: {graph_dir}")
|
|
208
|
+
if not graph_dir.is_dir():
|
|
209
|
+
raise EventOperationError(f"Not a directory: {graph_dir}")
|
|
210
|
+
|
|
211
|
+
from htmlgraph.event_log import JsonlEventLog
|
|
212
|
+
|
|
213
|
+
events_dir = graph_dir / "events"
|
|
214
|
+
|
|
215
|
+
if not events_dir.exists():
|
|
216
|
+
# No events directory means no events
|
|
217
|
+
return EventStats(
|
|
218
|
+
total_events=0,
|
|
219
|
+
session_count=0,
|
|
220
|
+
file_count=0,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
log = JsonlEventLog(events_dir)
|
|
225
|
+
|
|
226
|
+
# Count total events and track unique sessions
|
|
227
|
+
total_events = 0
|
|
228
|
+
sessions: set[str] = set()
|
|
229
|
+
|
|
230
|
+
for _, event in log.iter_events():
|
|
231
|
+
total_events += 1
|
|
232
|
+
if session_id := event.get("session_id"):
|
|
233
|
+
sessions.add(session_id)
|
|
234
|
+
|
|
235
|
+
# Count JSONL files
|
|
236
|
+
file_count = len(list(events_dir.glob("*.jsonl")))
|
|
237
|
+
|
|
238
|
+
return EventStats(
|
|
239
|
+
total_events=total_events,
|
|
240
|
+
session_count=len(sessions),
|
|
241
|
+
file_count=file_count,
|
|
242
|
+
)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
raise EventOperationError(f"Failed to get event stats: {e}") from e
|