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/event_log.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Event logging for HtmlGraph.
|
|
3
5
|
|
|
@@ -9,54 +11,98 @@ Design goals:
|
|
|
9
11
|
- Deterministic serialization for rebuildable analytics indexes
|
|
10
12
|
"""
|
|
11
13
|
|
|
12
|
-
from __future__ import annotations
|
|
13
14
|
|
|
14
15
|
import json
|
|
15
|
-
from dataclasses import dataclass
|
|
16
16
|
from datetime import datetime
|
|
17
17
|
from pathlib import Path
|
|
18
|
-
from typing import
|
|
18
|
+
from typing import TYPE_CHECKING, Any
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator
|
|
19
21
|
|
|
20
22
|
if TYPE_CHECKING:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class EventRecord(BaseModel):
|
|
27
|
+
"""
|
|
28
|
+
Event record for HtmlGraph tracking.
|
|
29
|
+
|
|
30
|
+
Uses Pydantic for automatic validation and serialization.
|
|
31
|
+
Immutable via ConfigDict(frozen=True).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
model_config = ConfigDict(frozen=True)
|
|
35
|
+
|
|
36
|
+
event_id: str = Field(..., min_length=1, description="Unique event identifier")
|
|
37
|
+
timestamp: datetime = Field(..., description="Event timestamp")
|
|
38
|
+
session_id: str = Field(..., min_length=1, description="Session identifier")
|
|
39
|
+
agent: str = Field(..., description="Agent name (e.g., 'claude', 'gemini')")
|
|
40
|
+
tool: str = Field(..., description="Tool used (e.g., 'Bash', 'Edit', 'Read')")
|
|
41
|
+
summary: str = Field(..., description="Human-readable event summary")
|
|
42
|
+
success: bool = Field(..., description="Whether the operation succeeded")
|
|
43
|
+
feature_id: str | None = Field(None, description="Associated feature ID")
|
|
44
|
+
drift_score: float | None = Field(None, description="Context drift score")
|
|
45
|
+
start_commit: str | None = Field(None, description="Starting git commit hash")
|
|
46
|
+
continued_from: str | None = Field(
|
|
47
|
+
None, description="Previous session ID if continued"
|
|
48
|
+
)
|
|
49
|
+
work_type: str | None = Field(None, description="WorkType enum value")
|
|
50
|
+
session_status: str | None = Field(None, description="Session status")
|
|
51
|
+
file_paths: list[str] | None = Field(None, description="Files involved in event")
|
|
52
|
+
payload: dict[str, Any] | None = Field(None, description="Additional event data")
|
|
53
|
+
parent_session_id: str | None = Field(
|
|
54
|
+
None, description="Parent session ID for subagents"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Phase 1: Enhanced Event Data Schema for multi-AI delegation tracking
|
|
58
|
+
delegated_to_ai: str | None = Field(
|
|
59
|
+
None, description="AI delegate: 'gemini', 'codex', 'copilot', 'claude', or None"
|
|
60
|
+
)
|
|
61
|
+
task_id: str | None = Field(
|
|
62
|
+
None, description="Unique task ID for parallel tracking"
|
|
63
|
+
)
|
|
64
|
+
task_status: str | None = Field(
|
|
65
|
+
None,
|
|
66
|
+
description="Task status: 'pending', 'running', 'completed', 'failed', 'timeout'",
|
|
67
|
+
)
|
|
68
|
+
model_selected: str | None = Field(
|
|
69
|
+
None, description="Specific model (e.g., 'gemini-2.0-flash')"
|
|
70
|
+
)
|
|
71
|
+
complexity_level: str | None = Field(
|
|
72
|
+
None, description="Complexity: 'low', 'medium', 'high', 'very-high'"
|
|
73
|
+
)
|
|
74
|
+
budget_mode: str | None = Field(
|
|
75
|
+
None, description="Budget mode: 'free', 'balanced', 'performance'"
|
|
76
|
+
)
|
|
77
|
+
execution_duration_seconds: float | None = Field(
|
|
78
|
+
None, description="Delegation execution time"
|
|
79
|
+
)
|
|
80
|
+
tokens_estimated: int | None = Field(None, description="Estimated token usage")
|
|
81
|
+
tokens_actual: int | None = Field(None, description="Actual token usage")
|
|
82
|
+
cost_usd: float | None = Field(None, description="Calculated cost in USD")
|
|
83
|
+
task_findings: str | None = Field(None, description="Results from delegated task")
|
|
84
|
+
|
|
85
|
+
@field_validator("event_id", "session_id")
|
|
86
|
+
@classmethod
|
|
87
|
+
def validate_non_empty_string(cls, v: str) -> str:
|
|
88
|
+
"""Ensure event_id and session_id are non-empty."""
|
|
89
|
+
if not v or not v.strip():
|
|
90
|
+
raise ValueError("Field must be a non-empty string")
|
|
91
|
+
return v
|
|
92
|
+
|
|
93
|
+
@field_serializer("timestamp")
|
|
94
|
+
def serialize_timestamp(self, timestamp: datetime) -> str:
|
|
95
|
+
"""Serialize timestamp to ISO format string."""
|
|
96
|
+
return timestamp.isoformat()
|
|
97
|
+
|
|
98
|
+
@field_serializer("file_paths")
|
|
99
|
+
def serialize_file_paths(self, file_paths: list[str] | None) -> list[str]:
|
|
100
|
+
"""Ensure file_paths is always a list (never None) in JSON output."""
|
|
101
|
+
return file_paths or []
|
|
41
102
|
|
|
42
103
|
def to_json(self) -> dict[str, Any]:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"timestamp": self.timestamp.isoformat(),
|
|
46
|
-
"session_id": self.session_id,
|
|
47
|
-
"agent": self.agent,
|
|
48
|
-
"tool": self.tool,
|
|
49
|
-
"summary": self.summary,
|
|
50
|
-
"success": self.success,
|
|
51
|
-
"feature_id": self.feature_id,
|
|
52
|
-
"work_type": self.work_type,
|
|
53
|
-
"drift_score": self.drift_score,
|
|
54
|
-
"start_commit": self.start_commit,
|
|
55
|
-
"continued_from": self.continued_from,
|
|
56
|
-
"session_status": self.session_status,
|
|
57
|
-
"file_paths": self.file_paths or [],
|
|
58
|
-
"payload": self.payload,
|
|
59
|
-
}
|
|
104
|
+
"""Convert EventRecord to JSON-serializable dictionary."""
|
|
105
|
+
return self.model_dump(mode="json")
|
|
60
106
|
|
|
61
107
|
|
|
62
108
|
class JsonlEventLog:
|
|
@@ -74,7 +120,10 @@ class JsonlEventLog:
|
|
|
74
120
|
|
|
75
121
|
def append(self, record: EventRecord) -> Path:
|
|
76
122
|
path = self.path_for_session(record.session_id)
|
|
77
|
-
line =
|
|
123
|
+
line = (
|
|
124
|
+
json.dumps(record.model_dump(mode="json"), ensure_ascii=False, default=str)
|
|
125
|
+
+ "\n"
|
|
126
|
+
)
|
|
78
127
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
79
128
|
|
|
80
129
|
# Best-effort dedupe: some producers (e.g. git hooks) may retry or be chained.
|
|
@@ -106,7 +155,7 @@ class JsonlEventLog:
|
|
|
106
155
|
f.write(line)
|
|
107
156
|
return path
|
|
108
157
|
|
|
109
|
-
def iter_events(self):
|
|
158
|
+
def iter_events(self) -> Any:
|
|
110
159
|
"""
|
|
111
160
|
Yield (path, event_dict) for all events across all JSONL files.
|
|
112
161
|
Skips malformed lines.
|
|
@@ -129,10 +178,7 @@ class JsonlEventLog:
|
|
|
129
178
|
continue
|
|
130
179
|
|
|
131
180
|
def get_session_events(
|
|
132
|
-
self,
|
|
133
|
-
session_id: str,
|
|
134
|
-
limit: int | None = None,
|
|
135
|
-
offset: int = 0
|
|
181
|
+
self, session_id: str, limit: int | None = None, offset: int = 0
|
|
136
182
|
) -> list[dict[str, Any]]:
|
|
137
183
|
"""
|
|
138
184
|
Get events for a specific session with pagination.
|
|
@@ -177,7 +223,7 @@ class JsonlEventLog:
|
|
|
177
223
|
tool: str | None = None,
|
|
178
224
|
feature_id: str | None = None,
|
|
179
225
|
since: Any = None, # datetime or ISO string
|
|
180
|
-
limit: int | None = None
|
|
226
|
+
limit: int | None = None,
|
|
181
227
|
) -> list[dict[str, Any]]:
|
|
182
228
|
"""
|
|
183
229
|
Query events with filters.
|
|
@@ -198,7 +244,7 @@ class JsonlEventLog:
|
|
|
198
244
|
since_dt = None
|
|
199
245
|
if since:
|
|
200
246
|
if isinstance(since, str):
|
|
201
|
-
since_dt = datetime.fromisoformat(since.replace(
|
|
247
|
+
since_dt = datetime.fromisoformat(since.replace("Z", "+00:00"))
|
|
202
248
|
else:
|
|
203
249
|
since_dt = since
|
|
204
250
|
|
|
@@ -213,19 +259,21 @@ class JsonlEventLog:
|
|
|
213
259
|
filtered: list[dict[str, Any]] = []
|
|
214
260
|
for evt in events:
|
|
215
261
|
# Tool filter
|
|
216
|
-
if tool and evt.get(
|
|
262
|
+
if tool and evt.get("tool") != tool:
|
|
217
263
|
continue
|
|
218
264
|
|
|
219
265
|
# Feature filter
|
|
220
|
-
if feature_id and evt.get(
|
|
266
|
+
if feature_id and evt.get("feature_id") != feature_id:
|
|
221
267
|
continue
|
|
222
268
|
|
|
223
269
|
# Timestamp filter
|
|
224
270
|
if since_dt:
|
|
225
|
-
evt_time_str = evt.get(
|
|
271
|
+
evt_time_str = evt.get("timestamp")
|
|
226
272
|
if evt_time_str and isinstance(evt_time_str, str):
|
|
227
273
|
try:
|
|
228
|
-
evt_time = datetime.fromisoformat(
|
|
274
|
+
evt_time = datetime.fromisoformat(
|
|
275
|
+
evt_time_str.replace("Z", "+00:00")
|
|
276
|
+
)
|
|
229
277
|
if evt_time < since_dt:
|
|
230
278
|
continue
|
|
231
279
|
except (ValueError, AttributeError):
|
htmlgraph/event_migration.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Helpers to migrate legacy session HTML activity logs to JSONL event logs.
|
|
3
5
|
"""
|
|
4
6
|
|
|
5
|
-
from __future__ import annotations
|
|
6
7
|
|
|
7
8
|
import json
|
|
8
9
|
from pathlib import Path
|
|
@@ -43,9 +44,15 @@ def export_sessions_to_jsonl(
|
|
|
43
44
|
# Serialize as one JSON object per line, oldest -> newest.
|
|
44
45
|
lines: list[str] = []
|
|
45
46
|
for entry in session.activity_log:
|
|
46
|
-
payload: dict[str, Any] | None =
|
|
47
|
+
payload: dict[str, Any] | None = (
|
|
48
|
+
entry.payload if isinstance(entry.payload, dict) else None
|
|
49
|
+
)
|
|
47
50
|
file_paths = None
|
|
48
|
-
if
|
|
51
|
+
if (
|
|
52
|
+
payload
|
|
53
|
+
and "file_paths" in payload
|
|
54
|
+
and isinstance(payload["file_paths"], list)
|
|
55
|
+
):
|
|
49
56
|
file_paths = payload["file_paths"]
|
|
50
57
|
|
|
51
58
|
event = {
|
|
@@ -66,7 +73,9 @@ def export_sessions_to_jsonl(
|
|
|
66
73
|
}
|
|
67
74
|
lines.append(json.dumps(event, ensure_ascii=False, default=str))
|
|
68
75
|
|
|
69
|
-
out_path.write_text(
|
|
76
|
+
out_path.write_text(
|
|
77
|
+
"\n".join(lines) + ("\n" if lines else ""), encoding="utf-8"
|
|
78
|
+
)
|
|
70
79
|
written += 1
|
|
71
80
|
|
|
72
81
|
return {"written": written, "skipped": skipped, "failed": failed}
|
htmlgraph/exceptions.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Custom exceptions for HtmlGraph."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HtmlGraphError(Exception):
|
|
5
|
+
"""Base exception for all HtmlGraph errors with debugging guidance."""
|
|
6
|
+
|
|
7
|
+
def __str__(self) -> str:
|
|
8
|
+
"""Return error message with debugging guidance."""
|
|
9
|
+
base_message = super().__str__()
|
|
10
|
+
guidance = (
|
|
11
|
+
"\n\n💡 Debugging help:"
|
|
12
|
+
"\n - See DEBUGGING.md for systematic troubleshooting"
|
|
13
|
+
"\n - Use researcher agent for unfamiliar errors"
|
|
14
|
+
"\n - Run 'htmlgraph --help' for available commands"
|
|
15
|
+
"\n - Run 'htmlgraph debug' for diagnostic tools"
|
|
16
|
+
)
|
|
17
|
+
return f"{base_message}{guidance}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class NodeNotFoundError(HtmlGraphError):
|
|
21
|
+
"""Raised when a node cannot be found."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, node_type: str, node_id: str):
|
|
24
|
+
self.node_type = node_type
|
|
25
|
+
self.node_id = node_id
|
|
26
|
+
super().__init__(f"{node_type.capitalize()} not found: {node_id}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SessionNotFoundError(HtmlGraphError):
|
|
30
|
+
"""Raised when a session cannot be found."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, session_id: str):
|
|
33
|
+
self.session_id = session_id
|
|
34
|
+
super().__init__(f"Session not found: {session_id}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ClaimConflictError(HtmlGraphError):
|
|
38
|
+
"""Raised when a claim operation conflicts with existing claim."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, node_id: str, current_owner: str):
|
|
41
|
+
self.node_id = node_id
|
|
42
|
+
self.current_owner = current_owner
|
|
43
|
+
super().__init__(f"Node {node_id} already claimed by {current_owner}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ValidationError(HtmlGraphError):
|
|
47
|
+
"""Raised when validation fails."""
|
|
48
|
+
|
|
49
|
+
pass
|
htmlgraph/file_watcher.py
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger(__name__)
|
|
4
|
+
|
|
1
5
|
"""
|
|
2
6
|
File watcher for automatic graph reloading.
|
|
3
7
|
|
|
4
8
|
Monitors .htmlgraph/**/*.html files and reloads collections when changes are detected.
|
|
5
9
|
"""
|
|
6
10
|
|
|
7
|
-
import
|
|
11
|
+
import fnmatch
|
|
8
12
|
import threading
|
|
13
|
+
from collections.abc import Callable
|
|
9
14
|
from pathlib import Path
|
|
10
|
-
from typing import
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
|
11
18
|
from watchdog.observers import Observer
|
|
12
|
-
|
|
19
|
+
|
|
20
|
+
# Collection-specific file patterns for smart filtering
|
|
21
|
+
COLLECTION_PATTERNS = {
|
|
22
|
+
"features": ["feat-*.html", "feature-*.html"],
|
|
23
|
+
"bugs": ["bug-*.html"],
|
|
24
|
+
"spikes": ["spk-*.html", "spike-*.html"],
|
|
25
|
+
"sessions": ["sess-*.html", "session-*.html"],
|
|
26
|
+
"tracks": ["trk-*.html", "track-*.html"],
|
|
27
|
+
"chores": ["chore-*.html"],
|
|
28
|
+
"insights": ["insi-*.html", "insight-*.html"],
|
|
29
|
+
"patterns": ["patt-*.html", "pattern-*.html"],
|
|
30
|
+
"metrics": ["metr-*.html", "metric-*.html"],
|
|
31
|
+
}
|
|
13
32
|
|
|
14
33
|
|
|
15
34
|
class GraphFileHandler(FileSystemEventHandler):
|
|
@@ -28,12 +47,34 @@ class GraphFileHandler(FileSystemEventHandler):
|
|
|
28
47
|
self.debounce_timer: threading.Timer | None = None
|
|
29
48
|
self.debounce_delay = 0.5 # 500ms debounce
|
|
30
49
|
|
|
31
|
-
def
|
|
50
|
+
def _is_relevant_file(self, filepath: str) -> bool:
|
|
51
|
+
"""
|
|
52
|
+
Check if changed file is relevant to this watcher's collection.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
filepath: Path to the file that changed
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if the file matches this collection's patterns
|
|
59
|
+
"""
|
|
60
|
+
filename = Path(filepath).name
|
|
61
|
+
|
|
62
|
+
# Skip non-HTML files
|
|
63
|
+
if not filename.endswith(".html"):
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
# Get patterns for this collection (default to all HTML if not found)
|
|
67
|
+
patterns = COLLECTION_PATTERNS.get(self.collection, ["*.html"])
|
|
68
|
+
|
|
69
|
+
# Check if filename matches any pattern
|
|
70
|
+
return any(fnmatch.fnmatch(filename, pattern) for pattern in patterns)
|
|
71
|
+
|
|
72
|
+
def _trigger_reload(self) -> None:
|
|
32
73
|
"""Trigger a reload after debounce delay."""
|
|
33
|
-
|
|
74
|
+
logger.info(f"[FileWatcher] Reloading collection: {self.collection}")
|
|
34
75
|
self.reload_callback()
|
|
35
76
|
|
|
36
|
-
def _debounced_reload(self):
|
|
77
|
+
def _debounced_reload(self) -> None:
|
|
37
78
|
"""Debounce rapid file changes to avoid excessive reloads."""
|
|
38
79
|
if self.debounce_timer:
|
|
39
80
|
self.debounce_timer.cancel()
|
|
@@ -41,29 +82,58 @@ class GraphFileHandler(FileSystemEventHandler):
|
|
|
41
82
|
self.debounce_timer = threading.Timer(self.debounce_delay, self._trigger_reload)
|
|
42
83
|
self.debounce_timer.start()
|
|
43
84
|
|
|
44
|
-
def on_created(self, event: FileSystemEvent):
|
|
85
|
+
def on_created(self, event: FileSystemEvent) -> None:
|
|
45
86
|
"""Handle file creation."""
|
|
46
|
-
if
|
|
47
|
-
|
|
48
|
-
|
|
87
|
+
if event.is_directory:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
# Skip if not relevant to our collection
|
|
91
|
+
if not self._is_relevant_file(str(event.src_path)):
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
logger.debug(
|
|
95
|
+
f"[FileWatcher] {self.collection}: File created - {Path(str(event.src_path)).name}"
|
|
96
|
+
)
|
|
97
|
+
self._debounced_reload()
|
|
49
98
|
|
|
50
|
-
def on_modified(self, event: FileSystemEvent):
|
|
99
|
+
def on_modified(self, event: FileSystemEvent) -> None:
|
|
51
100
|
"""Handle file modification."""
|
|
52
|
-
if
|
|
53
|
-
|
|
54
|
-
self._debounced_reload()
|
|
101
|
+
if event.is_directory:
|
|
102
|
+
return
|
|
55
103
|
|
|
56
|
-
|
|
104
|
+
# Skip if not relevant to our collection
|
|
105
|
+
if not self._is_relevant_file(str(event.src_path)):
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
logger.debug(
|
|
109
|
+
f"[FileWatcher] {self.collection}: File modified - {Path(str(event.src_path)).name}"
|
|
110
|
+
)
|
|
111
|
+
self._debounced_reload()
|
|
112
|
+
|
|
113
|
+
def on_deleted(self, event: FileSystemEvent) -> None:
|
|
57
114
|
"""Handle file deletion."""
|
|
58
|
-
if
|
|
59
|
-
|
|
60
|
-
|
|
115
|
+
if event.is_directory:
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
# Skip if not relevant to our collection
|
|
119
|
+
if not self._is_relevant_file(str(event.src_path)):
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
logger.debug(
|
|
123
|
+
f"[FileWatcher] {self.collection}: File deleted - {Path(str(event.src_path)).name}"
|
|
124
|
+
)
|
|
125
|
+
self._debounced_reload()
|
|
61
126
|
|
|
62
127
|
|
|
63
128
|
class GraphWatcher:
|
|
64
129
|
"""Watches graph directories and triggers reloads on changes."""
|
|
65
130
|
|
|
66
|
-
def __init__(
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
graph_dir: Path,
|
|
134
|
+
collections: list[str],
|
|
135
|
+
get_graph_callback: Callable[[str], Any],
|
|
136
|
+
) -> None:
|
|
67
137
|
"""
|
|
68
138
|
Initialize watcher.
|
|
69
139
|
|
|
@@ -78,9 +148,11 @@ class GraphWatcher:
|
|
|
78
148
|
self.observer = Observer()
|
|
79
149
|
self.handlers: dict[str, GraphFileHandler] = {}
|
|
80
150
|
|
|
81
|
-
def start(self):
|
|
151
|
+
def start(self) -> None:
|
|
82
152
|
"""Start watching for file changes."""
|
|
83
|
-
|
|
153
|
+
logger.info(
|
|
154
|
+
f"[FileWatcher] Starting file watcher for {len(self.collections)} collections..."
|
|
155
|
+
)
|
|
84
156
|
|
|
85
157
|
for collection in self.collections:
|
|
86
158
|
collection_dir = self.graph_dir / collection
|
|
@@ -88,11 +160,12 @@ class GraphWatcher:
|
|
|
88
160
|
continue
|
|
89
161
|
|
|
90
162
|
# Create handler with reload callback
|
|
91
|
-
def make_reload_callback(coll):
|
|
92
|
-
def reload():
|
|
163
|
+
def make_reload_callback(coll: str) -> Callable[[], None]:
|
|
164
|
+
def reload() -> None:
|
|
93
165
|
graph = self.get_graph_callback(coll)
|
|
94
166
|
count = graph.reload()
|
|
95
|
-
|
|
167
|
+
logger.info(f"[FileWatcher] Reloaded {count} nodes in {coll}")
|
|
168
|
+
|
|
96
169
|
return reload
|
|
97
170
|
|
|
98
171
|
handler = GraphFileHandler(collection, make_reload_callback(collection))
|
|
@@ -100,15 +173,15 @@ class GraphWatcher:
|
|
|
100
173
|
|
|
101
174
|
# Watch the collection directory
|
|
102
175
|
# Use recursive=True for tracks since they're stored in subdirectories
|
|
103
|
-
recursive =
|
|
176
|
+
recursive = collection == "tracks"
|
|
104
177
|
self.observer.schedule(handler, str(collection_dir), recursive=recursive)
|
|
105
178
|
|
|
106
179
|
self.observer.start()
|
|
107
|
-
|
|
180
|
+
logger.info(f"[FileWatcher] Watching {self.graph_dir} for changes...")
|
|
108
181
|
|
|
109
|
-
def stop(self):
|
|
182
|
+
def stop(self) -> None:
|
|
110
183
|
"""Stop watching for file changes."""
|
|
111
|
-
|
|
184
|
+
logger.info("[FileWatcher] Stopping file watcher...")
|
|
112
185
|
self.observer.stop()
|
|
113
186
|
self.observer.join()
|
|
114
187
|
|