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