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,339 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Analytics operations for HtmlGraph."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from htmlgraph import SDK
|
|
11
|
+
from htmlgraph.converter import html_to_session
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class AnalyticsSessionResult:
|
|
16
|
+
"""Result of analyzing a single session."""
|
|
17
|
+
|
|
18
|
+
session_id: str
|
|
19
|
+
metrics: dict[str, Any]
|
|
20
|
+
warnings: list[str]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class AnalyticsProjectResult:
|
|
25
|
+
"""Result of analyzing project-wide analytics."""
|
|
26
|
+
|
|
27
|
+
metrics: dict[str, Any]
|
|
28
|
+
warnings: list[str]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class RecommendationsResult:
|
|
33
|
+
"""Result of getting work recommendations."""
|
|
34
|
+
|
|
35
|
+
recommendations: list[dict[str, Any]]
|
|
36
|
+
reasoning: dict[str, Any]
|
|
37
|
+
warnings: list[str]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AnalyticsOperationError(RuntimeError):
|
|
41
|
+
"""Base error for analytics operations."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def analyze_session(*, graph_dir: Path, session_id: str) -> AnalyticsSessionResult:
|
|
45
|
+
"""
|
|
46
|
+
Compute analytics for a single session.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
graph_dir: Path to .htmlgraph directory
|
|
50
|
+
session_id: ID of the session to analyze
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
AnalyticsSessionResult with session metrics and warnings
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
AnalyticsOperationError: If session cannot be analyzed
|
|
57
|
+
"""
|
|
58
|
+
warnings: list[str] = []
|
|
59
|
+
|
|
60
|
+
# Validate inputs
|
|
61
|
+
if not graph_dir.exists():
|
|
62
|
+
raise AnalyticsOperationError(f"Graph directory does not exist: {graph_dir}")
|
|
63
|
+
|
|
64
|
+
session_path = graph_dir / "sessions" / f"{session_id}.html"
|
|
65
|
+
if not session_path.exists():
|
|
66
|
+
raise AnalyticsOperationError(f"Session not found: {session_id}")
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
# Load session
|
|
70
|
+
session = html_to_session(session_path)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
raise AnalyticsOperationError(f"Failed to load session {session_id}: {e}")
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
# Initialize SDK with minimal agent
|
|
76
|
+
sdk = SDK(directory=str(graph_dir), agent="analytics-ops")
|
|
77
|
+
|
|
78
|
+
# Compute metrics
|
|
79
|
+
metrics: dict[str, Any] = {}
|
|
80
|
+
|
|
81
|
+
# Work distribution
|
|
82
|
+
try:
|
|
83
|
+
work_dist = sdk.analytics.work_type_distribution(session_id=session_id)
|
|
84
|
+
metrics["work_distribution"] = work_dist
|
|
85
|
+
except Exception as e:
|
|
86
|
+
warnings.append(f"Failed to compute work distribution: {e}")
|
|
87
|
+
metrics["work_distribution"] = {}
|
|
88
|
+
|
|
89
|
+
# Spike-to-feature ratio
|
|
90
|
+
try:
|
|
91
|
+
spike_ratio = sdk.analytics.spike_to_feature_ratio(session_id=session_id)
|
|
92
|
+
metrics["spike_to_feature_ratio"] = spike_ratio
|
|
93
|
+
except Exception as e:
|
|
94
|
+
warnings.append(f"Failed to compute spike ratio: {e}")
|
|
95
|
+
metrics["spike_to_feature_ratio"] = 0.0
|
|
96
|
+
|
|
97
|
+
# Maintenance burden
|
|
98
|
+
try:
|
|
99
|
+
maintenance = sdk.analytics.maintenance_burden(session_id=session_id)
|
|
100
|
+
metrics["maintenance_burden"] = maintenance
|
|
101
|
+
except Exception as e:
|
|
102
|
+
warnings.append(f"Failed to compute maintenance burden: {e}")
|
|
103
|
+
metrics["maintenance_burden"] = 0.0
|
|
104
|
+
|
|
105
|
+
# Primary work type
|
|
106
|
+
try:
|
|
107
|
+
primary = sdk.analytics.calculate_session_primary_work_type(session_id)
|
|
108
|
+
metrics["primary_work_type"] = primary
|
|
109
|
+
except Exception as e:
|
|
110
|
+
warnings.append(f"Failed to compute primary work type: {e}")
|
|
111
|
+
metrics["primary_work_type"] = None
|
|
112
|
+
|
|
113
|
+
# Work breakdown (event counts)
|
|
114
|
+
try:
|
|
115
|
+
breakdown = sdk.analytics.calculate_session_work_breakdown(session_id)
|
|
116
|
+
metrics["work_breakdown"] = breakdown
|
|
117
|
+
metrics["total_events"] = sum(breakdown.values()) if breakdown else 0
|
|
118
|
+
except Exception as e:
|
|
119
|
+
warnings.append(f"Failed to compute work breakdown: {e}")
|
|
120
|
+
metrics["work_breakdown"] = {}
|
|
121
|
+
metrics["total_events"] = session.event_count
|
|
122
|
+
|
|
123
|
+
# Transition time metrics
|
|
124
|
+
try:
|
|
125
|
+
transition = sdk.analytics.transition_time_metrics(session_id=session_id)
|
|
126
|
+
metrics["transition_metrics"] = transition
|
|
127
|
+
except Exception as e:
|
|
128
|
+
warnings.append(f"Failed to compute transition metrics: {e}")
|
|
129
|
+
metrics["transition_metrics"] = {}
|
|
130
|
+
|
|
131
|
+
# Session metadata
|
|
132
|
+
metrics["session_id"] = session.id
|
|
133
|
+
metrics["agent"] = session.agent
|
|
134
|
+
metrics["status"] = session.status
|
|
135
|
+
metrics["started_at"] = session.started_at.isoformat()
|
|
136
|
+
if session.ended_at:
|
|
137
|
+
metrics["ended_at"] = session.ended_at.isoformat()
|
|
138
|
+
|
|
139
|
+
return AnalyticsSessionResult(
|
|
140
|
+
session_id=session_id, metrics=metrics, warnings=warnings
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
except AnalyticsOperationError:
|
|
144
|
+
raise
|
|
145
|
+
except Exception as e:
|
|
146
|
+
raise AnalyticsOperationError(f"Failed to analyze session {session_id}: {e}")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def analyze_project(*, graph_dir: Path) -> AnalyticsProjectResult:
|
|
150
|
+
"""
|
|
151
|
+
Compute analytics for the project.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
graph_dir: Path to .htmlgraph directory
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
AnalyticsProjectResult with project metrics and warnings
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
AnalyticsOperationError: If project cannot be analyzed
|
|
161
|
+
"""
|
|
162
|
+
warnings: list[str] = []
|
|
163
|
+
|
|
164
|
+
# Validate inputs
|
|
165
|
+
if not graph_dir.exists():
|
|
166
|
+
raise AnalyticsOperationError(f"Graph directory does not exist: {graph_dir}")
|
|
167
|
+
|
|
168
|
+
sessions_dir = graph_dir / "sessions"
|
|
169
|
+
if not sessions_dir.exists():
|
|
170
|
+
warnings.append("No sessions directory found")
|
|
171
|
+
return AnalyticsProjectResult(metrics={"total_sessions": 0}, warnings=warnings)
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
# Initialize SDK
|
|
175
|
+
sdk = SDK(directory=str(graph_dir), agent="analytics-ops")
|
|
176
|
+
|
|
177
|
+
# Get session count
|
|
178
|
+
session_files = sorted(
|
|
179
|
+
sessions_dir.glob("*.html"), key=lambda p: p.stat().st_mtime, reverse=True
|
|
180
|
+
)
|
|
181
|
+
total_sessions = len(session_files)
|
|
182
|
+
|
|
183
|
+
# Compute metrics
|
|
184
|
+
metrics: dict[str, Any] = {
|
|
185
|
+
"total_sessions": total_sessions,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if total_sessions == 0:
|
|
189
|
+
warnings.append("No sessions found in project")
|
|
190
|
+
return AnalyticsProjectResult(metrics=metrics, warnings=warnings)
|
|
191
|
+
|
|
192
|
+
# Project-wide work distribution
|
|
193
|
+
try:
|
|
194
|
+
work_dist = sdk.analytics.work_type_distribution()
|
|
195
|
+
metrics["work_distribution"] = work_dist
|
|
196
|
+
except Exception as e:
|
|
197
|
+
warnings.append(f"Failed to compute work distribution: {e}")
|
|
198
|
+
metrics["work_distribution"] = {}
|
|
199
|
+
|
|
200
|
+
# Project-wide spike-to-feature ratio
|
|
201
|
+
try:
|
|
202
|
+
spike_ratio = sdk.analytics.spike_to_feature_ratio()
|
|
203
|
+
metrics["spike_to_feature_ratio"] = spike_ratio
|
|
204
|
+
except Exception as e:
|
|
205
|
+
warnings.append(f"Failed to compute spike ratio: {e}")
|
|
206
|
+
metrics["spike_to_feature_ratio"] = 0.0
|
|
207
|
+
|
|
208
|
+
# Project-wide maintenance burden
|
|
209
|
+
try:
|
|
210
|
+
maintenance = sdk.analytics.maintenance_burden()
|
|
211
|
+
metrics["maintenance_burden"] = maintenance
|
|
212
|
+
except Exception as e:
|
|
213
|
+
warnings.append(f"Failed to compute maintenance burden: {e}")
|
|
214
|
+
metrics["maintenance_burden"] = 0.0
|
|
215
|
+
|
|
216
|
+
# Project-wide transition metrics
|
|
217
|
+
try:
|
|
218
|
+
transition = sdk.analytics.transition_time_metrics()
|
|
219
|
+
metrics["transition_metrics"] = transition
|
|
220
|
+
except Exception as e:
|
|
221
|
+
warnings.append(f"Failed to compute transition metrics: {e}")
|
|
222
|
+
metrics["transition_metrics"] = {}
|
|
223
|
+
|
|
224
|
+
# Session type breakdown
|
|
225
|
+
try:
|
|
226
|
+
from htmlgraph import WorkType
|
|
227
|
+
|
|
228
|
+
spike_sessions = sdk.analytics.get_sessions_by_work_type(
|
|
229
|
+
WorkType.SPIKE.value
|
|
230
|
+
)
|
|
231
|
+
feature_sessions = sdk.analytics.get_sessions_by_work_type(
|
|
232
|
+
WorkType.FEATURE.value
|
|
233
|
+
)
|
|
234
|
+
maintenance_sessions = sdk.analytics.get_sessions_by_work_type(
|
|
235
|
+
WorkType.MAINTENANCE.value
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
metrics["session_types"] = {
|
|
239
|
+
"spike": len(spike_sessions),
|
|
240
|
+
"feature": len(feature_sessions),
|
|
241
|
+
"maintenance": len(maintenance_sessions),
|
|
242
|
+
}
|
|
243
|
+
except Exception as e:
|
|
244
|
+
warnings.append(f"Failed to compute session types: {e}")
|
|
245
|
+
metrics["session_types"] = {}
|
|
246
|
+
|
|
247
|
+
# Recent sessions (metadata only)
|
|
248
|
+
try:
|
|
249
|
+
recent_sessions = []
|
|
250
|
+
for session_path in session_files[:5]: # Top 5 most recent
|
|
251
|
+
try:
|
|
252
|
+
session = html_to_session(session_path)
|
|
253
|
+
primary = (
|
|
254
|
+
sdk.analytics.calculate_session_primary_work_type(session.id)
|
|
255
|
+
or "unknown"
|
|
256
|
+
)
|
|
257
|
+
recent_sessions.append(
|
|
258
|
+
{
|
|
259
|
+
"session_id": session.id,
|
|
260
|
+
"agent": session.agent,
|
|
261
|
+
"started_at": session.started_at.isoformat(),
|
|
262
|
+
"status": session.status,
|
|
263
|
+
"primary_work_type": primary,
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
except Exception as e:
|
|
267
|
+
warnings.append(f"Failed to load session {session_path.name}: {e}")
|
|
268
|
+
continue
|
|
269
|
+
|
|
270
|
+
metrics["recent_sessions"] = recent_sessions
|
|
271
|
+
except Exception as e:
|
|
272
|
+
warnings.append(f"Failed to load recent sessions: {e}")
|
|
273
|
+
metrics["recent_sessions"] = []
|
|
274
|
+
|
|
275
|
+
return AnalyticsProjectResult(metrics=metrics, warnings=warnings)
|
|
276
|
+
|
|
277
|
+
except AnalyticsOperationError:
|
|
278
|
+
raise
|
|
279
|
+
except Exception as e:
|
|
280
|
+
raise AnalyticsOperationError(f"Failed to analyze project: {e}")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def get_recommendations(*, graph_dir: Path) -> RecommendationsResult:
|
|
284
|
+
"""
|
|
285
|
+
Get work recommendations based on project state.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
graph_dir: Path to .htmlgraph directory
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
RecommendationsResult with recommendations, reasoning, and warnings
|
|
292
|
+
|
|
293
|
+
Raises:
|
|
294
|
+
AnalyticsOperationError: If recommendations cannot be generated
|
|
295
|
+
"""
|
|
296
|
+
warnings: list[str] = []
|
|
297
|
+
|
|
298
|
+
# Validate inputs
|
|
299
|
+
if not graph_dir.exists():
|
|
300
|
+
raise AnalyticsOperationError(f"Graph directory does not exist: {graph_dir}")
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
# Initialize SDK
|
|
304
|
+
sdk = SDK(directory=str(graph_dir), agent="analytics-ops")
|
|
305
|
+
|
|
306
|
+
# Get recommendations
|
|
307
|
+
try:
|
|
308
|
+
task_recs = sdk.dep_analytics.recommend_next_tasks(agent_count=5)
|
|
309
|
+
recommendations = [
|
|
310
|
+
{
|
|
311
|
+
"id": rec.id,
|
|
312
|
+
"title": rec.title,
|
|
313
|
+
"priority": rec.priority,
|
|
314
|
+
"score": rec.score,
|
|
315
|
+
"reasons": rec.reasons,
|
|
316
|
+
"unlocks": rec.unlocks,
|
|
317
|
+
"estimated_effort": rec.estimated_effort,
|
|
318
|
+
}
|
|
319
|
+
for rec in task_recs.recommendations
|
|
320
|
+
]
|
|
321
|
+
reasoning = {
|
|
322
|
+
"recommendation_count": len(task_recs.recommendations),
|
|
323
|
+
"parallel_suggestions": task_recs.parallel_suggestions,
|
|
324
|
+
}
|
|
325
|
+
except Exception as e:
|
|
326
|
+
raise AnalyticsOperationError(f"Failed to generate recommendations: {e}")
|
|
327
|
+
|
|
328
|
+
# Add contextual warnings based on recommendations
|
|
329
|
+
if not recommendations:
|
|
330
|
+
warnings.append("No recommendations available - project may be empty")
|
|
331
|
+
|
|
332
|
+
return RecommendationsResult(
|
|
333
|
+
recommendations=recommendations, reasoning=reasoning, warnings=warnings
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
except AnalyticsOperationError:
|
|
337
|
+
raise
|
|
338
|
+
except Exception as e:
|
|
339
|
+
raise AnalyticsOperationError(f"Failed to get recommendations: {e}")
|
|
@@ -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
|
+
}
|