htmlgraph 0.26.25__py3-none-any.whl → 0.27.0__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/__init__.py +23 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/cli.py +3 -3
- htmlgraph/analytics/cost_analyzer.py +5 -1
- htmlgraph/analytics/cross_session.py +13 -9
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/work_type.py +15 -11
- htmlgraph/analytics_index.py +2 -1
- htmlgraph/api/main.py +58 -28
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/builders/base.py +2 -1
- htmlgraph/builders/bug.py +2 -1
- htmlgraph/builders/chore.py +2 -1
- htmlgraph/builders/epic.py +2 -1
- htmlgraph/builders/feature.py +2 -1
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +2 -1
- htmlgraph/builders/spike.py +2 -1
- htmlgraph/builders/track.py +2 -1
- htmlgraph/cli/analytics.py +2 -1
- htmlgraph/cli/base.py +2 -1
- htmlgraph/cli/core.py +2 -1
- htmlgraph/cli/main.py +2 -1
- htmlgraph/cli/models.py +2 -1
- htmlgraph/cli/templates/cost_dashboard.py +2 -1
- htmlgraph/cli/work/__init__.py +2 -1
- htmlgraph/cli/work/browse.py +2 -1
- htmlgraph/cli/work/features.py +2 -1
- htmlgraph/cli/work/orchestration.py +2 -1
- htmlgraph/cli/work/report.py +2 -1
- htmlgraph/cli/work/sessions.py +2 -1
- htmlgraph/cli/work/snapshot.py +2 -1
- htmlgraph/cli/work/tracks.py +2 -1
- htmlgraph/collections/base.py +10 -5
- 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 +12 -7
- htmlgraph/collections/spike.py +6 -1
- htmlgraph/collections/task_delegation.py +7 -2
- htmlgraph/collections/todo.py +2 -1
- htmlgraph/collections/traces.py +15 -10
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/edge_index.py +2 -1
- htmlgraph/event_log.py +81 -66
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +6 -2
- htmlgraph/hooks/cigs_pretool_enforcer.py +5 -1
- htmlgraph/hooks/drift_handler.py +3 -3
- htmlgraph/hooks/event_tracker.py +40 -61
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +4 -0
- htmlgraph/hooks/orchestrator_reflector.py +4 -0
- htmlgraph/hooks/post_tool_use_failure.py +7 -3
- htmlgraph/hooks/posttooluse.py +4 -0
- htmlgraph/hooks/prompt_analyzer.py +5 -5
- htmlgraph/hooks/session_handler.py +2 -1
- htmlgraph/hooks/session_summary.py +6 -2
- htmlgraph/hooks/validator.py +8 -4
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +2 -1
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/operations/analytics.py +2 -1
- htmlgraph/operations/bootstrap.py +2 -1
- htmlgraph/operations/events.py +2 -1
- htmlgraph/operations/fastapi_server.py +2 -1
- htmlgraph/operations/hooks.py +2 -1
- htmlgraph/operations/initialization.py +2 -1
- htmlgraph/operations/server.py +2 -1
- htmlgraph/orchestration/claude_launcher.py +23 -20
- htmlgraph/orchestration/command_builder.py +2 -1
- htmlgraph/orchestration/headless_spawner.py +6 -2
- htmlgraph/orchestration/model_selection.py +7 -3
- htmlgraph/orchestration/plugin_manager.py +24 -19
- htmlgraph/orchestration/spawners/claude.py +5 -2
- htmlgraph/orchestration/spawners/codex.py +12 -19
- htmlgraph/orchestration/spawners/copilot.py +13 -18
- htmlgraph/orchestration/spawners/gemini.py +12 -19
- htmlgraph/orchestration/subprocess_runner.py +6 -3
- htmlgraph/orchestration/task_coordination.py +16 -8
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/parallel.py +2 -1
- htmlgraph/query_builder.py +2 -1
- htmlgraph/reflection.py +2 -1
- htmlgraph/refs.py +2 -1
- htmlgraph/repo_hash.py +2 -1
- 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/server.py +21 -17
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/handoff.py +4 -3
- htmlgraph/system_prompts.py +2 -1
- htmlgraph/track_builder.py +2 -1
- htmlgraph/transcript.py +2 -1
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.0.dist-info}/METADATA +15 -1
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.0.dist-info}/RECORD +146 -114
- htmlgraph/sdk.py +0 -3500
- {htmlgraph-0.26.25.data → htmlgraph-0.27.0.data}/data/htmlgraph/dashboard.html +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.0.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.0.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.0.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.0.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.0.dist-info}/WHEEL +0 -0
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.0.dist-info}/entry_points.txt +0 -0
htmlgraph/__init__.py
CHANGED
|
@@ -5,6 +5,28 @@ A lightweight graph database framework using HTML files as nodes,
|
|
|
5
5
|
hyperlinks as edges, and CSS selectors as the query language.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.logging import RichHandler
|
|
12
|
+
|
|
13
|
+
# Configure Rich logging for entire SDK
|
|
14
|
+
# CRITICAL: Use stderr=True to prevent pollution of stdout (hooks output JSON to stdout)
|
|
15
|
+
logging.basicConfig(
|
|
16
|
+
handlers=[
|
|
17
|
+
RichHandler(
|
|
18
|
+
console=Console(stderr=True),
|
|
19
|
+
show_time=True,
|
|
20
|
+
show_level=True,
|
|
21
|
+
rich_tracebacks=True,
|
|
22
|
+
)
|
|
23
|
+
],
|
|
24
|
+
level=logging.INFO,
|
|
25
|
+
format="%(message)s",
|
|
26
|
+
datefmt="[%X]",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# SDK now lives in sdk/core.py - clean import from sdk package
|
|
8
30
|
from htmlgraph.agent_detection import detect_agent_name, get_agent_display_name
|
|
9
31
|
from htmlgraph.agents import AgentInterface
|
|
10
32
|
from htmlgraph.analytics import Analytics, DependencyAnalytics
|
|
@@ -96,7 +118,7 @@ from htmlgraph.types import (
|
|
|
96
118
|
)
|
|
97
119
|
from htmlgraph.work_type_utils import infer_work_type, infer_work_type_from_id
|
|
98
120
|
|
|
99
|
-
__version__ = "0.
|
|
121
|
+
__version__ = "0.27.0"
|
|
100
122
|
__all__ = [
|
|
101
123
|
# Exceptions
|
|
102
124
|
"HtmlGraphError",
|
htmlgraph/__init__.pyi
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type stub for htmlgraph package.
|
|
3
|
+
|
|
4
|
+
This stub provides type information for mypy when the SDK class
|
|
5
|
+
is loaded dynamically via importlib at runtime.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from htmlgraph.agent_detection import detect_agent_name as detect_agent_name
|
|
9
|
+
from htmlgraph.agent_detection import get_agent_display_name as get_agent_display_name
|
|
10
|
+
from htmlgraph.agents import AgentInterface as AgentInterface
|
|
11
|
+
from htmlgraph.analytics import Analytics as Analytics
|
|
12
|
+
|
|
13
|
+
# Analytics
|
|
14
|
+
from htmlgraph.analytics import DependencyAnalytics as DependencyAnalytics
|
|
15
|
+
from htmlgraph.atomic_ops import AtomicFileWriter as AtomicFileWriter
|
|
16
|
+
from htmlgraph.atomic_ops import DirectoryLocker as DirectoryLocker
|
|
17
|
+
from htmlgraph.atomic_ops import atomic_rename as atomic_rename
|
|
18
|
+
from htmlgraph.atomic_ops import (
|
|
19
|
+
cleanup_orphaned_temp_files as cleanup_orphaned_temp_files,
|
|
20
|
+
)
|
|
21
|
+
from htmlgraph.atomic_ops import safe_temp_file as safe_temp_file
|
|
22
|
+
from htmlgraph.atomic_ops import validate_atomic_write as validate_atomic_write
|
|
23
|
+
from htmlgraph.builders import BaseBuilder as BaseBuilder
|
|
24
|
+
from htmlgraph.builders import FeatureBuilder as FeatureBuilder
|
|
25
|
+
from htmlgraph.builders import SpikeBuilder as SpikeBuilder
|
|
26
|
+
from htmlgraph.collections import BaseCollection as BaseCollection
|
|
27
|
+
|
|
28
|
+
# Collections
|
|
29
|
+
from htmlgraph.collections import FeatureCollection as FeatureCollection
|
|
30
|
+
from htmlgraph.collections import SpikeCollection as SpikeCollection
|
|
31
|
+
from htmlgraph.context_analytics import ContextAnalytics as ContextAnalytics
|
|
32
|
+
from htmlgraph.context_analytics import ContextUsage as ContextUsage
|
|
33
|
+
from htmlgraph.decorators import RetryError as RetryError
|
|
34
|
+
from htmlgraph.decorators import retry as retry
|
|
35
|
+
from htmlgraph.decorators import retry_async as retry_async
|
|
36
|
+
from htmlgraph.edge_index import EdgeIndex as EdgeIndex
|
|
37
|
+
from htmlgraph.edge_index import EdgeRef as EdgeRef
|
|
38
|
+
from htmlgraph.exceptions import ClaimConflictError as ClaimConflictError
|
|
39
|
+
from htmlgraph.exceptions import HtmlGraphError as HtmlGraphError
|
|
40
|
+
from htmlgraph.exceptions import NodeNotFoundError as NodeNotFoundError
|
|
41
|
+
from htmlgraph.exceptions import SessionNotFoundError as SessionNotFoundError
|
|
42
|
+
from htmlgraph.exceptions import ValidationError as ValidationError
|
|
43
|
+
from htmlgraph.find_api import FindAPI as FindAPI
|
|
44
|
+
from htmlgraph.find_api import find as find
|
|
45
|
+
from htmlgraph.find_api import find_all as find_all
|
|
46
|
+
from htmlgraph.graph import CompiledQuery as CompiledQuery
|
|
47
|
+
from htmlgraph.graph import HtmlGraph as HtmlGraph
|
|
48
|
+
from htmlgraph.ids import generate_hierarchical_id as generate_hierarchical_id
|
|
49
|
+
from htmlgraph.ids import generate_id as generate_id
|
|
50
|
+
from htmlgraph.ids import is_legacy_id as is_legacy_id
|
|
51
|
+
from htmlgraph.ids import is_valid_id as is_valid_id
|
|
52
|
+
from htmlgraph.ids import parse_id as parse_id
|
|
53
|
+
from htmlgraph.learning import LearningPersistence as LearningPersistence
|
|
54
|
+
from htmlgraph.learning import (
|
|
55
|
+
auto_persist_on_session_end as auto_persist_on_session_end,
|
|
56
|
+
)
|
|
57
|
+
from htmlgraph.models import ActivityEntry as ActivityEntry
|
|
58
|
+
from htmlgraph.models import AggregatedMetric as AggregatedMetric
|
|
59
|
+
from htmlgraph.models import Chore as Chore
|
|
60
|
+
from htmlgraph.models import ContextSnapshot as ContextSnapshot
|
|
61
|
+
from htmlgraph.models import Edge as Edge
|
|
62
|
+
from htmlgraph.models import Graph as Graph
|
|
63
|
+
from htmlgraph.models import MaintenanceType as MaintenanceType
|
|
64
|
+
from htmlgraph.models import Node as Node
|
|
65
|
+
from htmlgraph.models import Pattern as Pattern
|
|
66
|
+
from htmlgraph.models import Session as Session
|
|
67
|
+
from htmlgraph.models import SessionInsight as SessionInsight
|
|
68
|
+
from htmlgraph.models import Spike as Spike
|
|
69
|
+
from htmlgraph.models import SpikeType as SpikeType
|
|
70
|
+
from htmlgraph.models import Step as Step
|
|
71
|
+
from htmlgraph.models import WorkType as WorkType
|
|
72
|
+
from htmlgraph.orchestration import delegate_with_id as delegate_with_id
|
|
73
|
+
from htmlgraph.orchestration import generate_task_id as generate_task_id
|
|
74
|
+
from htmlgraph.orchestration import get_results_by_task_id as get_results_by_task_id
|
|
75
|
+
from htmlgraph.orchestration import parallel_delegate as parallel_delegate
|
|
76
|
+
from htmlgraph.orchestrator_mode import OrchestratorMode as OrchestratorMode
|
|
77
|
+
from htmlgraph.orchestrator_mode import (
|
|
78
|
+
OrchestratorModeManager as OrchestratorModeManager,
|
|
79
|
+
)
|
|
80
|
+
from htmlgraph.parallel import AggregateResult as AggregateResult
|
|
81
|
+
from htmlgraph.parallel import ParallelAnalysis as ParallelAnalysis
|
|
82
|
+
from htmlgraph.parallel import ParallelWorkflow as ParallelWorkflow
|
|
83
|
+
from htmlgraph.query_builder import Condition as Condition
|
|
84
|
+
from htmlgraph.query_builder import Operator as Operator
|
|
85
|
+
from htmlgraph.query_builder import QueryBuilder as QueryBuilder
|
|
86
|
+
from htmlgraph.reflection import ComputationalReflection as ComputationalReflection
|
|
87
|
+
from htmlgraph.reflection import get_reflection_context as get_reflection_context
|
|
88
|
+
from htmlgraph.repo_hash import RepoHash as RepoHash
|
|
89
|
+
from htmlgraph.sdk.core import SDK as SDK
|
|
90
|
+
from htmlgraph.server import serve as serve
|
|
91
|
+
from htmlgraph.session_manager import SessionManager as SessionManager
|
|
92
|
+
from htmlgraph.session_registry import SessionRegistry as SessionRegistry
|
|
93
|
+
from htmlgraph.types import ActiveWorkItem as ActiveWorkItem
|
|
94
|
+
from htmlgraph.types import AggregateResultsDict as AggregateResultsDict
|
|
95
|
+
from htmlgraph.types import BottleneckDict as BottleneckDict
|
|
96
|
+
from htmlgraph.types import FeatureSummary as FeatureSummary
|
|
97
|
+
from htmlgraph.types import HighRiskTask as HighRiskTask
|
|
98
|
+
from htmlgraph.types import ImpactAnalysisDict as ImpactAnalysisDict
|
|
99
|
+
from htmlgraph.types import OrchestrationResult as OrchestrationResult
|
|
100
|
+
from htmlgraph.types import ParallelGuidelines as ParallelGuidelines
|
|
101
|
+
from htmlgraph.types import ParallelPlanResult as ParallelPlanResult
|
|
102
|
+
from htmlgraph.types import ParallelWorkInfo as ParallelWorkInfo
|
|
103
|
+
from htmlgraph.types import PlanningContext as PlanningContext
|
|
104
|
+
from htmlgraph.types import ProjectStatus as ProjectStatus
|
|
105
|
+
from htmlgraph.types import RiskAssessmentDict as RiskAssessmentDict
|
|
106
|
+
from htmlgraph.types import SessionAnalytics as SessionAnalytics
|
|
107
|
+
from htmlgraph.types import SessionStartInfo as SessionStartInfo
|
|
108
|
+
from htmlgraph.types import SessionSummary as SessionSummary
|
|
109
|
+
from htmlgraph.types import SmartPlanResult as SmartPlanResult
|
|
110
|
+
from htmlgraph.types import SubagentPrompt as SubagentPrompt
|
|
111
|
+
from htmlgraph.types import TaskPrompt as TaskPrompt
|
|
112
|
+
from htmlgraph.types import TrackCreationResult as TrackCreationResult
|
|
113
|
+
from htmlgraph.types import WorkQueueItem as WorkQueueItem
|
|
114
|
+
from htmlgraph.types import WorkRecommendation as WorkRecommendation
|
|
115
|
+
from htmlgraph.work_type_utils import infer_work_type as infer_work_type
|
|
116
|
+
from htmlgraph.work_type_utils import infer_work_type_from_id as infer_work_type_from_id
|
|
117
|
+
|
|
118
|
+
__version__: str
|
|
119
|
+
|
|
120
|
+
# SDK is imported from htmlgraph.sdk.core (see import above)
|
|
121
|
+
# Re-exported here for backward compatibility
|
|
122
|
+
|
|
123
|
+
__all__: list[str]
|
htmlgraph/agent_registry.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Agent Registry - Manages agent capabilities and routing.
|
|
3
5
|
|
|
@@ -7,7 +9,6 @@ Provides:
|
|
|
7
9
|
- Capability matching for task routing
|
|
8
10
|
"""
|
|
9
11
|
|
|
10
|
-
from __future__ import annotations
|
|
11
12
|
|
|
12
13
|
import json
|
|
13
14
|
from dataclasses import dataclass, field
|
htmlgraph/analytics/cli.py
CHANGED
|
@@ -109,7 +109,7 @@ def _iter_with_progress(
|
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
def _display_session_analytics(
|
|
112
|
-
console: Console, sdk: SDK, session_id: str, graph_dir: str, quiet: bool
|
|
112
|
+
console: Console, sdk: "SDK", session_id: str, graph_dir: str, quiet: bool
|
|
113
113
|
) -> None:
|
|
114
114
|
"""Display analytics for a single session."""
|
|
115
115
|
from htmlgraph.converter import html_to_session
|
|
@@ -221,7 +221,7 @@ def _display_session_analytics(
|
|
|
221
221
|
|
|
222
222
|
def _display_recent_sessions(
|
|
223
223
|
console: Console,
|
|
224
|
-
sdk: SDK,
|
|
224
|
+
sdk: "SDK",
|
|
225
225
|
session_files: list[Path],
|
|
226
226
|
graph_dir: str,
|
|
227
227
|
quiet: bool,
|
|
@@ -286,7 +286,7 @@ def _display_recent_sessions(
|
|
|
286
286
|
|
|
287
287
|
def _display_project_analytics(
|
|
288
288
|
console: Console,
|
|
289
|
-
sdk: SDK,
|
|
289
|
+
sdk: "SDK",
|
|
290
290
|
session_files: list[Path],
|
|
291
291
|
graph_dir: str,
|
|
292
292
|
quiet: bool,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger(__name__)
|
|
4
|
+
|
|
1
5
|
"""
|
|
2
6
|
CostAnalyzer for OTEL ROI Analysis - Phase 1.
|
|
3
7
|
|
|
@@ -15,7 +19,7 @@ Usage:
|
|
|
15
19
|
from htmlgraph.analytics.cost_analyzer import CostAnalyzer
|
|
16
20
|
analyzer = CostAnalyzer()
|
|
17
21
|
delegations = analyzer.get_task_delegations_with_costs()
|
|
18
|
-
|
|
22
|
+
logger.info(f"Total delegation cost: ${delegations['total_cost_usd']:.2f}")
|
|
19
23
|
"""
|
|
20
24
|
|
|
21
25
|
import sqlite3
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
1
7
|
"""
|
|
2
8
|
Cross-session analytics using Git commits as the continuity spine.
|
|
3
9
|
|
|
@@ -38,8 +44,6 @@ Example:
|
|
|
38
44
|
authors = cross.work_by_author(since_commit="abc123")
|
|
39
45
|
"""
|
|
40
46
|
|
|
41
|
-
from __future__ import annotations
|
|
42
|
-
|
|
43
47
|
import subprocess
|
|
44
48
|
from collections import defaultdict
|
|
45
49
|
from dataclasses import dataclass
|
|
@@ -147,8 +151,8 @@ class CrossSessionAnalytics:
|
|
|
147
151
|
... from_commit="HEAD~10",
|
|
148
152
|
... to_commit="HEAD"
|
|
149
153
|
... )
|
|
150
|
-
>>>
|
|
151
|
-
>>>
|
|
154
|
+
>>> logger.info(f"Total events: {report.total_events}")
|
|
155
|
+
>>> logger.info(f"Features: {', '.join(report.features)}")
|
|
152
156
|
"""
|
|
153
157
|
# Get commit list from Git
|
|
154
158
|
commits = self._get_commits_in_range(from_commit, to_commit)
|
|
@@ -273,7 +277,7 @@ class CrossSessionAnalytics:
|
|
|
273
277
|
|
|
274
278
|
Example:
|
|
275
279
|
>>> sessions = cross.sessions_for_feature("feature-auth")
|
|
276
|
-
>>>
|
|
280
|
+
>>> logger.info(f"Feature worked on in {len(sessions)} sessions")
|
|
277
281
|
"""
|
|
278
282
|
sessions = set()
|
|
279
283
|
|
|
@@ -320,8 +324,8 @@ class CrossSessionAnalytics:
|
|
|
320
324
|
|
|
321
325
|
Example:
|
|
322
326
|
>>> report = cross.feature_cross_session_report("feature-auth")
|
|
323
|
-
>>>
|
|
324
|
-
>>>
|
|
327
|
+
>>> logger.info(f"Implemented across {len(report.sessions)} sessions")
|
|
328
|
+
>>> logger.info(f"Duration: {report.duration_hours:.1f} hours")
|
|
325
329
|
"""
|
|
326
330
|
sessions = set()
|
|
327
331
|
commits = set()
|
|
@@ -395,7 +399,7 @@ class CrossSessionAnalytics:
|
|
|
395
399
|
Example:
|
|
396
400
|
>>> authors = cross.work_by_author(since_commit="v1.0.0")
|
|
397
401
|
>>> for email, stats in authors.items():
|
|
398
|
-
...
|
|
402
|
+
... logger.info(f"{email}: {stats['event_count']} events")
|
|
399
403
|
"""
|
|
400
404
|
authors: dict[str, dict[str, Any]] = defaultdict(
|
|
401
405
|
lambda: {
|
|
@@ -476,7 +480,7 @@ class CrossSessionAnalytics:
|
|
|
476
480
|
|
|
477
481
|
Example:
|
|
478
482
|
>>> commits = cross.commits_for_session("session-abc")
|
|
479
|
-
>>>
|
|
483
|
+
>>> logger.info(f"Session produced {len(commits)} commits")
|
|
480
484
|
"""
|
|
481
485
|
commits = set()
|
|
482
486
|
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
1
7
|
"""
|
|
2
8
|
Dependency-aware analytics for HtmlGraph.
|
|
3
9
|
|
|
@@ -9,8 +15,6 @@ Provides advanced graph analysis for project management:
|
|
|
9
15
|
- Work prioritization
|
|
10
16
|
"""
|
|
11
17
|
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
18
|
from collections import deque
|
|
15
19
|
from typing import TYPE_CHECKING
|
|
16
20
|
|
|
@@ -55,12 +59,12 @@ class DependencyAnalytics:
|
|
|
55
59
|
# Find bottlenecks (cached internally for performance)
|
|
56
60
|
bottlenecks = dep.find_bottlenecks(top_n=5)
|
|
57
61
|
for bn in bottlenecks:
|
|
58
|
-
|
|
62
|
+
logger.info(f"{bn.title} blocks {bn.transitive_blocking} features")
|
|
59
63
|
|
|
60
64
|
# Get work recommendations (reuses cached data)
|
|
61
65
|
recs = dep.recommend_next_tasks(agent_count=3)
|
|
62
66
|
for rec in recs.recommendations:
|
|
63
|
-
|
|
67
|
+
logger.info(f"Work on: {rec.title} (unlocks {len(rec.unlocks)} features)")
|
|
64
68
|
|
|
65
69
|
# After making graph changes, invalidate cache
|
|
66
70
|
sdk.features.update(feature_id, status="done")
|
|
@@ -201,7 +205,7 @@ class DependencyAnalytics:
|
|
|
201
205
|
|
|
202
206
|
Example:
|
|
203
207
|
report = dep.find_parallelizable_work(status="todo")
|
|
204
|
-
|
|
208
|
+
logger.info(f"Can work on {report.max_parallelism} features in parallel")
|
|
205
209
|
"""
|
|
206
210
|
# Get dependency levels (topological layers)
|
|
207
211
|
levels = self.dependency_levels(status_filter=[status])
|
|
@@ -456,7 +460,7 @@ class DependencyAnalytics:
|
|
|
456
460
|
Example:
|
|
457
461
|
recs = dep.recommend_next_tasks(agent_count=3)
|
|
458
462
|
for rec in recs.recommendations:
|
|
459
|
-
|
|
463
|
+
logger.info(f"Work on: {rec.title}")
|
|
460
464
|
"""
|
|
461
465
|
# Get all nodes with target status
|
|
462
466
|
candidates = [n for n in self.graph.nodes.values() if n.status == status]
|
htmlgraph/analytics/work_type.py
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
1
7
|
"""
|
|
2
8
|
Analytics API for HtmlGraph work type analysis.
|
|
3
9
|
|
|
@@ -25,8 +31,6 @@ Example:
|
|
|
25
31
|
# Returns: 25.5 (% of work spent on maintenance)
|
|
26
32
|
"""
|
|
27
33
|
|
|
28
|
-
from __future__ import annotations
|
|
29
|
-
|
|
30
34
|
from datetime import datetime, timezone
|
|
31
35
|
from typing import TYPE_CHECKING
|
|
32
36
|
|
|
@@ -97,7 +101,7 @@ class Analytics:
|
|
|
97
101
|
Example:
|
|
98
102
|
>>> analytics = sdk.analytics
|
|
99
103
|
>>> dist = analytics.work_type_distribution(session_id="session-123")
|
|
100
|
-
>>>
|
|
104
|
+
>>> logger.info("%s", dist)
|
|
101
105
|
{
|
|
102
106
|
"feature-implementation": 45.2,
|
|
103
107
|
"spike-investigation": 28.3,
|
|
@@ -163,11 +167,11 @@ class Analytics:
|
|
|
163
167
|
|
|
164
168
|
Example:
|
|
165
169
|
>>> ratio = sdk.analytics.spike_to_feature_ratio(session_id="session-123")
|
|
166
|
-
>>>
|
|
170
|
+
>>> logger.info(f"Spike-to-feature ratio: {ratio:.2f}")
|
|
167
171
|
Spike-to-feature ratio: 0.63
|
|
168
172
|
|
|
169
173
|
>>> if ratio > 0.5:
|
|
170
|
-
...
|
|
174
|
+
... logger.info("This was a research-heavy session")
|
|
171
175
|
"""
|
|
172
176
|
events = self._get_events(session_id, start_date, end_date)
|
|
173
177
|
|
|
@@ -221,11 +225,11 @@ class Analytics:
|
|
|
221
225
|
|
|
222
226
|
Example:
|
|
223
227
|
>>> burden = sdk.analytics.maintenance_burden(session_id="session-123")
|
|
224
|
-
>>>
|
|
228
|
+
>>> logger.info(f"Maintenance burden: {burden:.1f}%")
|
|
225
229
|
Maintenance burden: 32.5%
|
|
226
230
|
|
|
227
231
|
>>> if burden > 40:
|
|
228
|
-
...
|
|
232
|
+
... logger.info("⚠️ High maintenance burden - consider addressing technical debt")
|
|
229
233
|
"""
|
|
230
234
|
events = self._get_events(session_id, start_date, end_date)
|
|
231
235
|
|
|
@@ -276,7 +280,7 @@ class Analytics:
|
|
|
276
280
|
>>> spike_sessions = sdk.analytics.get_sessions_by_work_type(
|
|
277
281
|
... "spike-investigation"
|
|
278
282
|
... )
|
|
279
|
-
>>>
|
|
283
|
+
>>> logger.info(f"Found {len(spike_sessions)} exploratory sessions")
|
|
280
284
|
"""
|
|
281
285
|
session_nodes = self.sdk.sessions.all()
|
|
282
286
|
matching_sessions = []
|
|
@@ -316,7 +320,7 @@ class Analytics:
|
|
|
316
320
|
|
|
317
321
|
Example:
|
|
318
322
|
>>> breakdown = sdk.analytics.calculate_session_work_breakdown("session-123")
|
|
319
|
-
>>>
|
|
323
|
+
>>> logger.info("%s", breakdown)
|
|
320
324
|
{
|
|
321
325
|
"feature-implementation": 45,
|
|
322
326
|
"spike-investigation": 28,
|
|
@@ -344,7 +348,7 @@ class Analytics:
|
|
|
344
348
|
|
|
345
349
|
Example:
|
|
346
350
|
>>> primary = sdk.analytics.calculate_session_primary_work_type("session-123")
|
|
347
|
-
>>>
|
|
351
|
+
>>> logger.info(f"Primary work type: {primary}")
|
|
348
352
|
Primary work type: spike-investigation
|
|
349
353
|
"""
|
|
350
354
|
session = self._get_session(session_id)
|
|
@@ -399,7 +403,7 @@ class Analytics:
|
|
|
399
403
|
|
|
400
404
|
Example:
|
|
401
405
|
>>> metrics = sdk.analytics.transition_time_metrics(session_id="session-123")
|
|
402
|
-
>>>
|
|
406
|
+
>>> logger.info(f"Transition time: {metrics['transition_percent']:.1f}%")
|
|
403
407
|
Transition time: 15.3%
|
|
404
408
|
"""
|
|
405
409
|
from pathlib import Path
|
htmlgraph/analytics_index.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
SQLite analytics index for HtmlGraph event logs.
|
|
3
5
|
|
|
@@ -5,7 +7,6 @@ This is a rebuildable cache/index for fast dashboard queries.
|
|
|
5
7
|
The canonical source of truth is the JSONL event log under `.htmlgraph/events/`.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
|
-
from __future__ import annotations
|
|
9
10
|
|
|
10
11
|
import json
|
|
11
12
|
import sqlite3
|
htmlgraph/api/main.py
CHANGED
|
@@ -204,7 +204,37 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
204
204
|
return "0"
|
|
205
205
|
return f"{value:,}"
|
|
206
206
|
|
|
207
|
+
def format_duration(seconds: float | int | None) -> str:
|
|
208
|
+
"""Format duration in seconds to human-readable string."""
|
|
209
|
+
if seconds is None:
|
|
210
|
+
return "0.00s"
|
|
211
|
+
return f"{float(seconds):.2f}s"
|
|
212
|
+
|
|
213
|
+
def format_bytes(bytes_size: int | float | None) -> str:
|
|
214
|
+
"""Format bytes to MB with 2 decimal places."""
|
|
215
|
+
if bytes_size is None:
|
|
216
|
+
return "0.00MB"
|
|
217
|
+
return f"{int(bytes_size) / (1024 * 1024):.2f}MB"
|
|
218
|
+
|
|
219
|
+
def truncate_text(text: str | None, length: int = 50) -> str:
|
|
220
|
+
"""Truncate text to specified length with ellipsis."""
|
|
221
|
+
if text is None:
|
|
222
|
+
return ""
|
|
223
|
+
return text[:length] + "..." if len(text) > length else text
|
|
224
|
+
|
|
225
|
+
def format_timestamp(ts: Any) -> str:
|
|
226
|
+
"""Format timestamp to readable string."""
|
|
227
|
+
if ts is None:
|
|
228
|
+
return ""
|
|
229
|
+
if hasattr(ts, "strftime"):
|
|
230
|
+
return str(ts.strftime("%Y-%m-%d %H:%M:%S"))
|
|
231
|
+
return str(ts)
|
|
232
|
+
|
|
207
233
|
templates.env.filters["format_number"] = format_number
|
|
234
|
+
templates.env.filters["format_duration"] = format_duration
|
|
235
|
+
templates.env.filters["format_bytes"] = format_bytes
|
|
236
|
+
templates.env.filters["truncate"] = truncate_text
|
|
237
|
+
templates.env.filters["format_timestamp"] = format_timestamp
|
|
208
238
|
|
|
209
239
|
# Setup static files
|
|
210
240
|
static_dir = Path(__file__).parent / "static"
|
|
@@ -287,8 +317,8 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
287
317
|
|
|
288
318
|
# Execute query with timing
|
|
289
319
|
exec_start = time.time()
|
|
290
|
-
|
|
291
|
-
|
|
320
|
+
async with db.execute(query) as cursor:
|
|
321
|
+
rows = await cursor.fetchall()
|
|
292
322
|
exec_time_ms = (time.time() - exec_start) * 1000
|
|
293
323
|
|
|
294
324
|
agents = []
|
|
@@ -434,8 +464,8 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
434
464
|
|
|
435
465
|
# Execute query with timing
|
|
436
466
|
exec_start = time.time()
|
|
437
|
-
|
|
438
|
-
|
|
467
|
+
async with db.execute(query, params) as cursor:
|
|
468
|
+
rows = await cursor.fetchall()
|
|
439
469
|
exec_time_ms = (time.time() - exec_start) * 1000
|
|
440
470
|
|
|
441
471
|
# Build result models
|
|
@@ -485,15 +515,15 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
485
515
|
(SELECT COUNT(DISTINCT agent_id) FROM agent_events) as total_agents,
|
|
486
516
|
(SELECT COUNT(*) FROM sessions) as total_sessions
|
|
487
517
|
"""
|
|
488
|
-
|
|
489
|
-
|
|
518
|
+
async with db.execute(stats_query) as cursor:
|
|
519
|
+
row = await cursor.fetchone()
|
|
490
520
|
|
|
491
521
|
# Query distinct agent IDs for the agent set
|
|
492
522
|
agents_query = (
|
|
493
523
|
"SELECT DISTINCT agent_id FROM agent_events WHERE agent_id IS NOT NULL"
|
|
494
524
|
)
|
|
495
|
-
|
|
496
|
-
|
|
525
|
+
async with db.execute(agents_query) as agents_cursor:
|
|
526
|
+
agents_rows = await agents_cursor.fetchall()
|
|
497
527
|
agents = [row[0] for row in agents_rows]
|
|
498
528
|
|
|
499
529
|
if row is None:
|
|
@@ -616,8 +646,8 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
616
646
|
parent_query += " ORDER BY timestamp DESC LIMIT ?"
|
|
617
647
|
parent_params.append(limit)
|
|
618
648
|
|
|
619
|
-
|
|
620
|
-
|
|
649
|
+
async with db.execute(parent_query, parent_params) as cursor:
|
|
650
|
+
parent_rows = await cursor.fetchall()
|
|
621
651
|
|
|
622
652
|
traces: list[dict[str, Any]] = []
|
|
623
653
|
|
|
@@ -655,8 +685,8 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
655
685
|
WHERE parent_event_id = ?
|
|
656
686
|
ORDER BY timestamp ASC
|
|
657
687
|
"""
|
|
658
|
-
|
|
659
|
-
|
|
688
|
+
async with db.execute(child_query, (parent_event_id,)) as child_cursor:
|
|
689
|
+
child_rows = await child_cursor.fetchall()
|
|
660
690
|
|
|
661
691
|
child_events = []
|
|
662
692
|
for child_row in child_rows:
|
|
@@ -806,8 +836,8 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
806
836
|
params.append(limit)
|
|
807
837
|
|
|
808
838
|
exec_start = time.time()
|
|
809
|
-
|
|
810
|
-
|
|
839
|
+
async with db.execute(query, params) as cursor:
|
|
840
|
+
rows = await cursor.fetchall()
|
|
811
841
|
|
|
812
842
|
for row in rows:
|
|
813
843
|
events.append(
|
|
@@ -851,8 +881,8 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
851
881
|
spike_query += " ORDER BY created_at DESC LIMIT ?"
|
|
852
882
|
spike_params.append(limit)
|
|
853
883
|
|
|
854
|
-
|
|
855
|
-
|
|
884
|
+
async with db.execute(spike_query, spike_params) as spike_cursor:
|
|
885
|
+
spike_rows = await spike_cursor.fetchall()
|
|
856
886
|
|
|
857
887
|
for row in spike_rows:
|
|
858
888
|
events.append(
|
|
@@ -902,8 +932,8 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
902
932
|
collab_query += " ORDER BY timestamp DESC LIMIT ?"
|
|
903
933
|
collab_params.append(limit)
|
|
904
934
|
|
|
905
|
-
|
|
906
|
-
|
|
935
|
+
async with db.execute(collab_query, collab_params) as collab_cursor:
|
|
936
|
+
collab_rows = await collab_cursor.fetchall()
|
|
907
937
|
|
|
908
938
|
for row in collab_rows:
|
|
909
939
|
events.append(
|
|
@@ -1016,8 +1046,8 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
1016
1046
|
LIMIT ?
|
|
1017
1047
|
"""
|
|
1018
1048
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1049
|
+
async with db.execute(user_query_query, [limit]) as cursor:
|
|
1050
|
+
user_query_rows = await cursor.fetchall()
|
|
1021
1051
|
|
|
1022
1052
|
conversation_turns: list[dict[str, Any]] = []
|
|
1023
1053
|
|
|
@@ -1061,8 +1091,8 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
1061
1091
|
if depth >= max_depth:
|
|
1062
1092
|
return [], 0.0, 0, 0
|
|
1063
1093
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1094
|
+
async with db.execute(children_query, [parent_id]) as cursor:
|
|
1095
|
+
rows = await cursor.fetchall()
|
|
1066
1096
|
|
|
1067
1097
|
children_list: list[dict[str, Any]] = []
|
|
1068
1098
|
total_dur = 0.0
|
|
@@ -1338,8 +1368,8 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
1338
1368
|
query += " ORDER BY started_at DESC LIMIT ? OFFSET ?"
|
|
1339
1369
|
params.extend([limit, offset])
|
|
1340
1370
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1371
|
+
async with db.execute(query, params) as cursor:
|
|
1372
|
+
rows = await cursor.fetchall()
|
|
1343
1373
|
|
|
1344
1374
|
# Get total count for pagination
|
|
1345
1375
|
count_query = "SELECT COUNT(*) FROM sessions WHERE 1=1"
|
|
@@ -1348,8 +1378,8 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
1348
1378
|
count_query += " AND status = ?"
|
|
1349
1379
|
count_params.append(status)
|
|
1350
1380
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1381
|
+
async with db.execute(count_query, count_params) as count_cursor:
|
|
1382
|
+
count_row = await count_cursor.fetchone()
|
|
1353
1383
|
total = int(count_row[0]) if count_row else 0
|
|
1354
1384
|
|
|
1355
1385
|
# Build session objects
|
|
@@ -1416,8 +1446,8 @@ def get_app(db_path: str) -> FastAPI:
|
|
|
1416
1446
|
LIMIT 50
|
|
1417
1447
|
"""
|
|
1418
1448
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1449
|
+
async with db.execute(query) as cursor:
|
|
1450
|
+
rows = list(await cursor.fetchall())
|
|
1421
1451
|
logger.debug(f"orchestration_view: Query executed, got {len(rows)} rows")
|
|
1422
1452
|
|
|
1423
1453
|
delegations = []
|
htmlgraph/attribute_index.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Attribute Index for O(1) attribute-based lookups.
|
|
3
5
|
|
|
@@ -11,7 +13,6 @@ Without this index, finding nodes by attribute requires scanning
|
|
|
11
13
|
all nodes in the graph - O(n) complexity.
|
|
12
14
|
"""
|
|
13
15
|
|
|
14
|
-
from __future__ import annotations
|
|
15
16
|
|
|
16
17
|
from collections import defaultdict
|
|
17
18
|
from dataclasses import dataclass, field
|
htmlgraph/builders/base.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Base builder class for fluent node creation.
|
|
3
5
|
|
|
4
6
|
Provides common builder patterns shared across all node types.
|
|
5
7
|
"""
|
|
6
8
|
|
|
7
|
-
from __future__ import annotations
|
|
8
9
|
|
|
9
10
|
from datetime import datetime
|
|
10
11
|
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
htmlgraph/builders/bug.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Bug builder for creating bug report nodes.
|
|
3
5
|
|
|
@@ -5,7 +7,6 @@ Extends BaseBuilder with bug-specific methods like
|
|
|
5
7
|
severity and reproduction steps.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
|
-
from __future__ import annotations
|
|
9
10
|
|
|
10
11
|
from typing import TYPE_CHECKING, Any
|
|
11
12
|
|