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
htmlgraph/config.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HtmlGraph Configuration Management.
|
|
3
|
+
|
|
4
|
+
This module provides centralized configuration management using Pydantic Settings,
|
|
5
|
+
allowing configuration from environment variables, .env files, and CLI arguments.
|
|
6
|
+
|
|
7
|
+
IMPORTANT: Database path functions (get_database_path, get_analytics_cache_path)
|
|
8
|
+
are lightweight and have NO dependencies. They can be imported anywhere.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
# =============================================================================
|
|
16
|
+
# LIGHTWEIGHT DATABASE PATH FUNCTIONS (NO DEPENDENCIES)
|
|
17
|
+
# These MUST come before any heavy imports so spawners can use them
|
|
18
|
+
# =============================================================================
|
|
19
|
+
|
|
20
|
+
# Database filenames (SINGLE SOURCE OF TRUTH)
|
|
21
|
+
DATABASE_FILENAME = "htmlgraph.db" # Unified event database
|
|
22
|
+
ANALYTICS_CACHE_FILENAME = "index.sqlite" # Analytics cache (rebuildable)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_database_path(project_root: Path | str | None = None) -> Path:
|
|
26
|
+
"""
|
|
27
|
+
Get the unified database path for event tracking.
|
|
28
|
+
|
|
29
|
+
This is the SINGLE source of truth for database location.
|
|
30
|
+
All hooks, agents, and spawners MUST use this function.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
project_root: Optional project root path. If None, uses HTMLGRAPH_PROJECT_ROOT
|
|
34
|
+
env var or current working directory.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Path to htmlgraph.db (the unified event database)
|
|
38
|
+
"""
|
|
39
|
+
if project_root is None:
|
|
40
|
+
project_root = Path(os.environ.get("HTMLGRAPH_PROJECT_ROOT", os.getcwd()))
|
|
41
|
+
else:
|
|
42
|
+
project_root = Path(project_root)
|
|
43
|
+
|
|
44
|
+
return project_root / ".htmlgraph" / DATABASE_FILENAME
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_analytics_cache_path(project_root: Path | str | None = None) -> Path:
|
|
48
|
+
"""
|
|
49
|
+
Get the analytics cache database path.
|
|
50
|
+
|
|
51
|
+
This is for read-only analytics queries (rebuildable from events).
|
|
52
|
+
NOT for event tracking - use get_database_path() for that.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
project_root: Optional project root path. If None, uses HTMLGRAPH_PROJECT_ROOT
|
|
56
|
+
env var or current working directory.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Path to index.sqlite (analytics cache, gitignored)
|
|
60
|
+
"""
|
|
61
|
+
if project_root is None:
|
|
62
|
+
project_root = Path(os.environ.get("HTMLGRAPH_PROJECT_ROOT", os.getcwd()))
|
|
63
|
+
else:
|
|
64
|
+
project_root = Path(project_root)
|
|
65
|
+
|
|
66
|
+
return project_root / ".htmlgraph" / ANALYTICS_CACHE_FILENAME
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# =============================================================================
|
|
70
|
+
# PYDANTIC CONFIGURATION (Heavy imports below - spawners don't need this)
|
|
71
|
+
# =============================================================================
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
from pydantic_settings import BaseSettings
|
|
75
|
+
|
|
76
|
+
_PYDANTIC_AVAILABLE = True
|
|
77
|
+
except ImportError:
|
|
78
|
+
_PYDANTIC_AVAILABLE = False
|
|
79
|
+
BaseSettings = object # type: ignore
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if _PYDANTIC_AVAILABLE:
|
|
83
|
+
|
|
84
|
+
class HtmlGraphConfig(BaseSettings):
|
|
85
|
+
"""Global HtmlGraph configuration using Pydantic Settings.
|
|
86
|
+
|
|
87
|
+
Configuration can be provided via:
|
|
88
|
+
1. Environment variables (prefix: HTMLGRAPH_)
|
|
89
|
+
2. .env file
|
|
90
|
+
3. Direct instantiation with parameters
|
|
91
|
+
4. CLI argument overrides
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
# Core paths
|
|
95
|
+
graph_dir: Path = Path.home() / ".htmlgraph"
|
|
96
|
+
|
|
97
|
+
# Database paths (SINGLE SOURCE OF TRUTH)
|
|
98
|
+
# All hooks, agents, and spawners MUST use these via get_database_path()
|
|
99
|
+
database_filename: str = "htmlgraph.db" # Unified event database
|
|
100
|
+
analytics_cache_filename: str = "index.sqlite" # Analytics cache (rebuildable)
|
|
101
|
+
|
|
102
|
+
# Feature tracking
|
|
103
|
+
features_dir: Path | None = None
|
|
104
|
+
sessions_dir: Path | None = None
|
|
105
|
+
spikes_dir: Path | None = None
|
|
106
|
+
tracks_dir: Path | None = None
|
|
107
|
+
archives_dir: Path | None = None
|
|
108
|
+
|
|
109
|
+
# CLI behavior
|
|
110
|
+
debug: bool = False
|
|
111
|
+
verbose: bool = False
|
|
112
|
+
auto_sync: bool = True
|
|
113
|
+
color_output: bool = True
|
|
114
|
+
|
|
115
|
+
# Session management
|
|
116
|
+
max_sessions: int = 100
|
|
117
|
+
session_retention_days: int = 30
|
|
118
|
+
auto_archive_sessions: bool = True
|
|
119
|
+
|
|
120
|
+
# Performance
|
|
121
|
+
max_query_results: int = 1000
|
|
122
|
+
cache_enabled: bool = True
|
|
123
|
+
cache_ttl_seconds: int = 3600
|
|
124
|
+
|
|
125
|
+
# Logging
|
|
126
|
+
log_level: str = "INFO"
|
|
127
|
+
log_file: Path | None = None
|
|
128
|
+
|
|
129
|
+
model_config = {
|
|
130
|
+
"env_prefix": "HTMLGRAPH_",
|
|
131
|
+
"env_file": ".env",
|
|
132
|
+
"case_sensitive": False,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
def __init__(self, **data: Any) -> None:
|
|
136
|
+
"""Initialize config and compute derived paths."""
|
|
137
|
+
super().__init__(**data)
|
|
138
|
+
# Compute derived paths if not explicitly set
|
|
139
|
+
if self.features_dir is None:
|
|
140
|
+
self.features_dir = self.graph_dir / "features"
|
|
141
|
+
if self.sessions_dir is None:
|
|
142
|
+
self.sessions_dir = self.graph_dir / "sessions"
|
|
143
|
+
if self.spikes_dir is None:
|
|
144
|
+
self.spikes_dir = self.graph_dir / "spikes"
|
|
145
|
+
if self.tracks_dir is None:
|
|
146
|
+
self.tracks_dir = self.graph_dir / "tracks"
|
|
147
|
+
if self.archives_dir is None:
|
|
148
|
+
self.archives_dir = self.graph_dir / "archives"
|
|
149
|
+
|
|
150
|
+
def ensure_directories(self) -> None:
|
|
151
|
+
"""Create all configured directories if they don't exist."""
|
|
152
|
+
for directory in [
|
|
153
|
+
self.graph_dir,
|
|
154
|
+
self.features_dir,
|
|
155
|
+
self.sessions_dir,
|
|
156
|
+
self.spikes_dir,
|
|
157
|
+
self.tracks_dir,
|
|
158
|
+
self.archives_dir,
|
|
159
|
+
]:
|
|
160
|
+
if directory:
|
|
161
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
|
|
163
|
+
def get_config_dict(self) -> dict[str, Any]:
|
|
164
|
+
"""Get configuration as dictionary."""
|
|
165
|
+
return {
|
|
166
|
+
"graph_dir": str(self.graph_dir),
|
|
167
|
+
"features_dir": str(self.features_dir),
|
|
168
|
+
"sessions_dir": str(self.sessions_dir),
|
|
169
|
+
"spikes_dir": str(self.spikes_dir),
|
|
170
|
+
"tracks_dir": str(self.tracks_dir),
|
|
171
|
+
"archives_dir": str(self.archives_dir),
|
|
172
|
+
"debug": self.debug,
|
|
173
|
+
"verbose": self.verbose,
|
|
174
|
+
"auto_sync": self.auto_sync,
|
|
175
|
+
"color_output": self.color_output,
|
|
176
|
+
"max_sessions": self.max_sessions,
|
|
177
|
+
"session_retention_days": self.session_retention_days,
|
|
178
|
+
"auto_archive_sessions": self.auto_archive_sessions,
|
|
179
|
+
"max_query_results": self.max_query_results,
|
|
180
|
+
"cache_enabled": self.cache_enabled,
|
|
181
|
+
"cache_ttl_seconds": self.cache_ttl_seconds,
|
|
182
|
+
"log_level": self.log_level,
|
|
183
|
+
"log_file": str(self.log_file) if self.log_file else None,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Global configuration instance
|
|
187
|
+
config: HtmlGraphConfig = HtmlGraphConfig()
|
|
188
|
+
else:
|
|
189
|
+
# Pydantic not available - config object won't work but database functions will
|
|
190
|
+
config = None # type: ignore
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Context Analytics for HtmlGraph
|
|
5
|
+
|
|
6
|
+
Provides hierarchical context usage tracking and analytics:
|
|
7
|
+
Activity → Session → Feature → Track
|
|
8
|
+
|
|
9
|
+
Enables drill-down analysis of where context was consumed.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from htmlgraph.sdk import SDK
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ContextUsage:
|
|
22
|
+
"""Aggregated context usage at any level of the hierarchy."""
|
|
23
|
+
|
|
24
|
+
tokens_used: int = 0
|
|
25
|
+
peak_tokens: int = 0
|
|
26
|
+
cost_usd: float = 0.0
|
|
27
|
+
output_tokens: int = 0
|
|
28
|
+
|
|
29
|
+
# Breakdown by child entities
|
|
30
|
+
by_feature: dict[str, int] = field(default_factory=dict)
|
|
31
|
+
by_session: dict[str, int] = field(default_factory=dict)
|
|
32
|
+
by_tool: dict[str, int] = field(default_factory=dict)
|
|
33
|
+
|
|
34
|
+
# Metadata
|
|
35
|
+
entity_type: str = "" # "track", "feature", "session", "activity"
|
|
36
|
+
entity_id: str = ""
|
|
37
|
+
entity_title: str = ""
|
|
38
|
+
|
|
39
|
+
def add_child(self, child_id: str, child_usage: ContextUsage) -> None:
|
|
40
|
+
"""Add a child's usage to this aggregate."""
|
|
41
|
+
self.tokens_used += child_usage.tokens_used
|
|
42
|
+
self.peak_tokens = max(self.peak_tokens, child_usage.peak_tokens)
|
|
43
|
+
self.cost_usd += child_usage.cost_usd
|
|
44
|
+
self.output_tokens += child_usage.output_tokens
|
|
45
|
+
|
|
46
|
+
# Track by child entity
|
|
47
|
+
if child_usage.entity_type == "feature":
|
|
48
|
+
self.by_feature[child_id] = child_usage.tokens_used
|
|
49
|
+
elif child_usage.entity_type == "session":
|
|
50
|
+
self.by_session[child_id] = child_usage.tokens_used
|
|
51
|
+
|
|
52
|
+
# Merge tool breakdown
|
|
53
|
+
for tool, count in child_usage.by_tool.items():
|
|
54
|
+
self.by_tool[tool] = self.by_tool.get(tool, 0) + count
|
|
55
|
+
|
|
56
|
+
def to_dict(self) -> dict:
|
|
57
|
+
"""Convert to dictionary for serialization."""
|
|
58
|
+
return {
|
|
59
|
+
"entity_type": self.entity_type,
|
|
60
|
+
"entity_id": self.entity_id,
|
|
61
|
+
"entity_title": self.entity_title,
|
|
62
|
+
"tokens_used": self.tokens_used,
|
|
63
|
+
"peak_tokens": self.peak_tokens,
|
|
64
|
+
"cost_usd": self.cost_usd,
|
|
65
|
+
"output_tokens": self.output_tokens,
|
|
66
|
+
"by_feature": self.by_feature,
|
|
67
|
+
"by_session": self.by_session,
|
|
68
|
+
"by_tool": self.by_tool,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ContextAnalytics:
|
|
73
|
+
"""
|
|
74
|
+
Hierarchical context usage analytics.
|
|
75
|
+
|
|
76
|
+
Provides drill-down from Track → Feature → Session → Activity.
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
>>> sdk = SDK(agent="claude")
|
|
80
|
+
>>> ctx = ContextAnalytics(sdk)
|
|
81
|
+
>>>
|
|
82
|
+
>>> # Get track-level usage
|
|
83
|
+
>>> track_usage = ctx.get_track_usage("track-auth")
|
|
84
|
+
>>> print(f"Total: {track_usage.tokens_used:,} tokens")
|
|
85
|
+
>>>
|
|
86
|
+
>>> # Drill down to features
|
|
87
|
+
>>> for feat_id, tokens in track_usage.by_feature.items():
|
|
88
|
+
... print(f" {feat_id}: {tokens:,}")
|
|
89
|
+
>>>
|
|
90
|
+
>>> # Get detailed feature breakdown
|
|
91
|
+
>>> feat_usage = ctx.get_feature_usage("feat-login")
|
|
92
|
+
>>> print(f"Peak: {feat_usage.peak_tokens:,}")
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, sdk: SDK):
|
|
96
|
+
"""Initialize with SDK reference."""
|
|
97
|
+
self._sdk = sdk
|
|
98
|
+
|
|
99
|
+
def get_session_usage(self, session_id: str) -> ContextUsage:
|
|
100
|
+
"""
|
|
101
|
+
Get context usage for a specific session.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
session_id: Session ID to analyze
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
ContextUsage with session-level metrics
|
|
108
|
+
"""
|
|
109
|
+
session = self._sdk.session_manager.get_session(session_id)
|
|
110
|
+
if not session:
|
|
111
|
+
return ContextUsage(entity_type="session", entity_id=session_id)
|
|
112
|
+
|
|
113
|
+
usage = ContextUsage(
|
|
114
|
+
entity_type="session",
|
|
115
|
+
entity_id=session.id,
|
|
116
|
+
entity_title=session.title,
|
|
117
|
+
tokens_used=0,
|
|
118
|
+
peak_tokens=session.peak_context_tokens,
|
|
119
|
+
cost_usd=session.total_cost_usd,
|
|
120
|
+
output_tokens=session.total_tokens_generated,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Aggregate from context snapshots
|
|
124
|
+
for snapshot in session.context_snapshots:
|
|
125
|
+
usage.tokens_used = max(usage.tokens_used, snapshot.current_tokens)
|
|
126
|
+
if snapshot.feature_id:
|
|
127
|
+
prev = usage.by_feature.get(snapshot.feature_id, 0)
|
|
128
|
+
usage.by_feature[snapshot.feature_id] = max(
|
|
129
|
+
prev, snapshot.current_tokens
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Also use context_by_feature from session
|
|
133
|
+
for feat_id, tokens in session.context_by_feature.items():
|
|
134
|
+
usage.by_feature[feat_id] = max(usage.by_feature.get(feat_id, 0), tokens)
|
|
135
|
+
|
|
136
|
+
# Get tool breakdown from activity log
|
|
137
|
+
for activity in session.activity_log:
|
|
138
|
+
usage.by_tool[activity.tool] = usage.by_tool.get(activity.tool, 0) + 1
|
|
139
|
+
|
|
140
|
+
return usage
|
|
141
|
+
|
|
142
|
+
def get_feature_usage(self, feature_id: str) -> ContextUsage:
|
|
143
|
+
"""
|
|
144
|
+
Get context usage for a specific feature.
|
|
145
|
+
|
|
146
|
+
Aggregates from all sessions that worked on this feature.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
feature_id: Feature ID to analyze
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
ContextUsage with feature-level metrics
|
|
153
|
+
"""
|
|
154
|
+
feature = self._sdk.features.get(feature_id)
|
|
155
|
+
if not feature:
|
|
156
|
+
return ContextUsage(entity_type="feature", entity_id=feature_id)
|
|
157
|
+
|
|
158
|
+
usage = ContextUsage(
|
|
159
|
+
entity_type="feature",
|
|
160
|
+
entity_id=feature.id,
|
|
161
|
+
entity_title=feature.title,
|
|
162
|
+
tokens_used=feature.context_tokens_used,
|
|
163
|
+
peak_tokens=feature.context_peak_tokens,
|
|
164
|
+
cost_usd=feature.context_cost_usd,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Get usage from each session that worked on this feature
|
|
168
|
+
for session_id in feature.context_sessions:
|
|
169
|
+
session_usage = self.get_session_usage(session_id)
|
|
170
|
+
usage.by_session[session_id] = session_usage.tokens_used
|
|
171
|
+
|
|
172
|
+
# Merge tool breakdown
|
|
173
|
+
for tool, count in session_usage.by_tool.items():
|
|
174
|
+
usage.by_tool[tool] = usage.by_tool.get(tool, 0) + count
|
|
175
|
+
|
|
176
|
+
return usage
|
|
177
|
+
|
|
178
|
+
def get_track_usage(self, track_id: str) -> ContextUsage:
|
|
179
|
+
"""
|
|
180
|
+
Get context usage for a track (aggregate of all features).
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
track_id: Track ID to analyze
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
ContextUsage with track-level metrics
|
|
187
|
+
"""
|
|
188
|
+
# Get all features in this track
|
|
189
|
+
features = self._sdk.features.where(track_id=track_id)
|
|
190
|
+
|
|
191
|
+
usage = ContextUsage(
|
|
192
|
+
entity_type="track",
|
|
193
|
+
entity_id=track_id,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Aggregate from each feature
|
|
197
|
+
for feature in features:
|
|
198
|
+
feat_usage = self.get_feature_usage(feature.id)
|
|
199
|
+
usage.add_child(feature.id, feat_usage)
|
|
200
|
+
|
|
201
|
+
return usage
|
|
202
|
+
|
|
203
|
+
def get_all_tracks_usage(self) -> list[ContextUsage]:
|
|
204
|
+
"""
|
|
205
|
+
Get context usage for all tracks.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
List of ContextUsage objects, one per track
|
|
209
|
+
"""
|
|
210
|
+
# Find all unique track IDs
|
|
211
|
+
track_ids: set[str] = set()
|
|
212
|
+
for feature in self._sdk.features.all():
|
|
213
|
+
if feature.track_id:
|
|
214
|
+
track_ids.add(feature.track_id)
|
|
215
|
+
|
|
216
|
+
return [self.get_track_usage(tid) for tid in sorted(track_ids)]
|
|
217
|
+
|
|
218
|
+
def get_total_usage(self) -> ContextUsage:
|
|
219
|
+
"""
|
|
220
|
+
Get total context usage across all sessions.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
ContextUsage with project-wide metrics
|
|
224
|
+
"""
|
|
225
|
+
usage = ContextUsage(
|
|
226
|
+
entity_type="project",
|
|
227
|
+
entity_id="total",
|
|
228
|
+
entity_title="All Sessions",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Get all sessions
|
|
232
|
+
sessions = self._sdk.sessions.all()
|
|
233
|
+
for session in sessions:
|
|
234
|
+
session_usage = self.get_session_usage(session.id)
|
|
235
|
+
usage.add_child(session.id, session_usage)
|
|
236
|
+
|
|
237
|
+
return usage
|
|
238
|
+
|
|
239
|
+
def get_usage_by_work_type(self) -> dict[str, ContextUsage]:
|
|
240
|
+
"""
|
|
241
|
+
Get context usage grouped by work type.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Dictionary mapping work type to ContextUsage
|
|
245
|
+
"""
|
|
246
|
+
by_work_type: dict[str, ContextUsage] = {}
|
|
247
|
+
|
|
248
|
+
for feature in self._sdk.features.all():
|
|
249
|
+
# Infer work type from feature
|
|
250
|
+
work_type = feature.properties.get("work_type", "feature")
|
|
251
|
+
|
|
252
|
+
if work_type not in by_work_type:
|
|
253
|
+
by_work_type[work_type] = ContextUsage(
|
|
254
|
+
entity_type="work_type",
|
|
255
|
+
entity_id=work_type,
|
|
256
|
+
entity_title=work_type.replace("-", " ").title(),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
feat_usage = self.get_feature_usage(feature.id)
|
|
260
|
+
by_work_type[work_type].add_child(feature.id, feat_usage)
|
|
261
|
+
|
|
262
|
+
return by_work_type
|
|
263
|
+
|
|
264
|
+
def context_efficiency_report(self) -> dict[str, Any]:
|
|
265
|
+
"""
|
|
266
|
+
Generate a context efficiency report.
|
|
267
|
+
|
|
268
|
+
Identifies:
|
|
269
|
+
- Features with highest context consumption
|
|
270
|
+
- Sessions with highest peak usage
|
|
271
|
+
- Cost breakdown by feature/track
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Report dictionary with efficiency metrics
|
|
275
|
+
"""
|
|
276
|
+
total = self.get_total_usage()
|
|
277
|
+
|
|
278
|
+
# Get top features by context usage
|
|
279
|
+
feature_usages = []
|
|
280
|
+
for feature in self._sdk.features.all():
|
|
281
|
+
feat_usage = self.get_feature_usage(feature.id)
|
|
282
|
+
feature_usages.append((feature.id, feature.title, feat_usage))
|
|
283
|
+
|
|
284
|
+
# Sort by tokens used descending
|
|
285
|
+
feature_usages.sort(key=lambda x: x[2].tokens_used, reverse=True)
|
|
286
|
+
|
|
287
|
+
top_features = [
|
|
288
|
+
{
|
|
289
|
+
"id": fid,
|
|
290
|
+
"title": title,
|
|
291
|
+
"tokens": usage.tokens_used,
|
|
292
|
+
"cost": usage.cost_usd,
|
|
293
|
+
"sessions": len(usage.by_session),
|
|
294
|
+
}
|
|
295
|
+
for fid, title, usage in feature_usages[:10]
|
|
296
|
+
]
|
|
297
|
+
|
|
298
|
+
# Cost per feature
|
|
299
|
+
total_cost = total.cost_usd
|
|
300
|
+
cost_efficiency = []
|
|
301
|
+
for fid, title, usage in feature_usages:
|
|
302
|
+
if usage.cost_usd > 0:
|
|
303
|
+
cost_efficiency.append(
|
|
304
|
+
{
|
|
305
|
+
"id": fid,
|
|
306
|
+
"title": title,
|
|
307
|
+
"cost": usage.cost_usd,
|
|
308
|
+
"percent_of_total": (usage.cost_usd / total_cost * 100)
|
|
309
|
+
if total_cost > 0
|
|
310
|
+
else 0,
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
"total_tokens": total.tokens_used,
|
|
316
|
+
"total_cost": total.cost_usd,
|
|
317
|
+
"total_output_tokens": total.output_tokens,
|
|
318
|
+
"peak_tokens": total.peak_tokens,
|
|
319
|
+
"features_count": len(feature_usages),
|
|
320
|
+
"sessions_count": len(total.by_session),
|
|
321
|
+
"top_features_by_context": top_features,
|
|
322
|
+
"cost_by_feature": cost_efficiency[:10],
|
|
323
|
+
"by_tool": total.by_tool,
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
def drill_down(self, entity_type: str, entity_id: str) -> ContextUsage:
|
|
327
|
+
"""
|
|
328
|
+
Drill down into a specific entity.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
entity_type: "track", "feature", or "session"
|
|
332
|
+
entity_id: Entity ID
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
ContextUsage for the entity
|
|
336
|
+
"""
|
|
337
|
+
if entity_type == "track":
|
|
338
|
+
return self.get_track_usage(entity_id)
|
|
339
|
+
elif entity_type == "feature":
|
|
340
|
+
return self.get_feature_usage(entity_id)
|
|
341
|
+
elif entity_type == "session":
|
|
342
|
+
return self.get_session_usage(entity_id)
|
|
343
|
+
else:
|
|
344
|
+
return ContextUsage(entity_type=entity_type, entity_id=entity_id)
|