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
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
HtmlGraph Session Handler Module
|
|
5
|
+
|
|
6
|
+
Centralizes session lifecycle and tracking logic for hooks.
|
|
7
|
+
Provides unified functions for session initialization, tracking, and cleanup.
|
|
8
|
+
|
|
9
|
+
This module extracts common patterns from session-start.py and session-end.py
|
|
10
|
+
hooks to provide reusable session management operations.
|
|
11
|
+
|
|
12
|
+
Public API:
|
|
13
|
+
init_or_get_session(context: HookContext) -> Session | None
|
|
14
|
+
Get or create session from SessionManager
|
|
15
|
+
|
|
16
|
+
handle_session_start(context: HookContext, session: Session | None) -> dict
|
|
17
|
+
Initialize HtmlGraph tracking and build feature context
|
|
18
|
+
|
|
19
|
+
handle_session_end(context: HookContext) -> dict
|
|
20
|
+
Close session gracefully and record final metrics
|
|
21
|
+
|
|
22
|
+
record_user_query_event(context: HookContext, prompt: str) -> str | None
|
|
23
|
+
Create UserQuery event in database
|
|
24
|
+
|
|
25
|
+
check_version_status() -> dict | None
|
|
26
|
+
Check if HtmlGraph has updates available
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import logging
|
|
32
|
+
import os
|
|
33
|
+
import subprocess
|
|
34
|
+
from datetime import datetime, timezone
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import TYPE_CHECKING, Any
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from htmlgraph.hooks.context import HookContext
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def init_or_get_session(context: HookContext) -> Any | None:
|
|
45
|
+
"""
|
|
46
|
+
Get or create session from SessionManager.
|
|
47
|
+
|
|
48
|
+
Attempts to get an active session for the current agent.
|
|
49
|
+
If none exists, creates a new one with automatic initialization.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
context: HookContext with project and graph directory information
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Session object if successful, None if SessionManager unavailable or error occurs
|
|
56
|
+
|
|
57
|
+
Note:
|
|
58
|
+
- Handles graceful degradation if SessionManager cannot be imported
|
|
59
|
+
- Caches session in context for reuse
|
|
60
|
+
- Logs session ID for debugging
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
manager = context.session_manager
|
|
64
|
+
agent = context.agent_id
|
|
65
|
+
|
|
66
|
+
# Try to get existing session for this agent
|
|
67
|
+
active = manager.get_active_session_for_agent(agent=agent)
|
|
68
|
+
if not active:
|
|
69
|
+
# Create new session with commit info
|
|
70
|
+
try:
|
|
71
|
+
head_commit = _get_head_commit(context.project_dir)
|
|
72
|
+
except Exception:
|
|
73
|
+
head_commit = None
|
|
74
|
+
|
|
75
|
+
active = manager.start_session(
|
|
76
|
+
session_id=None,
|
|
77
|
+
agent=agent,
|
|
78
|
+
start_commit=head_commit,
|
|
79
|
+
title=f"Session {datetime.now().strftime('%Y-%m-%d %H:%M')}",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
context.log("info", f"Session initialized: {active.id if active else 'None'}")
|
|
83
|
+
return active
|
|
84
|
+
|
|
85
|
+
except ImportError as e:
|
|
86
|
+
context.log("error", f"SessionManager not available: {e}")
|
|
87
|
+
return None
|
|
88
|
+
except Exception as e:
|
|
89
|
+
context.log("error", f"Failed to initialize session: {e}")
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def handle_session_start(context: HookContext, session: Any | None) -> dict[str, Any]:
|
|
94
|
+
"""
|
|
95
|
+
Initialize HtmlGraph tracking for the session.
|
|
96
|
+
|
|
97
|
+
Performs session startup operations:
|
|
98
|
+
- Initializes database entry if needed
|
|
99
|
+
- Loads active features and spikes from project
|
|
100
|
+
- Builds feature context string
|
|
101
|
+
- Records session start event
|
|
102
|
+
- Creates conversation-init spike if new conversation
|
|
103
|
+
- Injects concurrent session and recent work context
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
context: HookContext with project and graph directory information
|
|
107
|
+
session: Session object from SessionManager (optional)
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
dict with:
|
|
111
|
+
{
|
|
112
|
+
"continue": True,
|
|
113
|
+
"hookSpecificOutput": {
|
|
114
|
+
"sessionFeatureContext": str with feature context,
|
|
115
|
+
"sessionContext": optional concurrent/recent work context,
|
|
116
|
+
"versionInfo": optional version check result
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
"""
|
|
120
|
+
output: dict[str, Any] = {
|
|
121
|
+
"continue": True,
|
|
122
|
+
"hookSpecificOutput": {
|
|
123
|
+
"sessionFeatureContext": "",
|
|
124
|
+
"sessionContext": "",
|
|
125
|
+
"versionInfo": None,
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if not session:
|
|
130
|
+
return output
|
|
131
|
+
|
|
132
|
+
# Ensure session exists in database
|
|
133
|
+
try:
|
|
134
|
+
db = context.database
|
|
135
|
+
cursor = db.connection.cursor()
|
|
136
|
+
cursor.execute(
|
|
137
|
+
"SELECT COUNT(*) FROM sessions WHERE session_id = ?",
|
|
138
|
+
(session.id,),
|
|
139
|
+
)
|
|
140
|
+
session_exists = cursor.fetchone()[0] > 0
|
|
141
|
+
|
|
142
|
+
if not session_exists:
|
|
143
|
+
cursor.execute(
|
|
144
|
+
"""
|
|
145
|
+
INSERT INTO sessions (session_id, agent_assigned, created_at, status)
|
|
146
|
+
VALUES (?, ?, ?, 'active')
|
|
147
|
+
""",
|
|
148
|
+
(
|
|
149
|
+
session.id,
|
|
150
|
+
context.agent_id,
|
|
151
|
+
datetime.now(timezone.utc).isoformat(),
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
db.connection.commit()
|
|
155
|
+
context.log("info", f"Created database session: {session.id}")
|
|
156
|
+
except ImportError:
|
|
157
|
+
context.log("debug", "Database not available, skipping session entry")
|
|
158
|
+
except Exception as e:
|
|
159
|
+
context.log("warning", f"Could not create database session: {e}")
|
|
160
|
+
|
|
161
|
+
# Track session start activity
|
|
162
|
+
try:
|
|
163
|
+
external_session_id = context.hook_input.get("session_id", "unknown")
|
|
164
|
+
context.session_manager.track_activity(
|
|
165
|
+
session_id=session.id,
|
|
166
|
+
tool="SessionStart",
|
|
167
|
+
summary=f"Session started: {external_session_id}",
|
|
168
|
+
payload={
|
|
169
|
+
"agent": context.agent_id,
|
|
170
|
+
"external_session_id": external_session_id,
|
|
171
|
+
},
|
|
172
|
+
)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
context.log("warning", f"Could not track session start activity: {e}")
|
|
175
|
+
|
|
176
|
+
# Load features and build context
|
|
177
|
+
try:
|
|
178
|
+
features = _load_features(context.graph_dir)
|
|
179
|
+
active_features = [f for f in features if f.get("status") == "in-progress"]
|
|
180
|
+
|
|
181
|
+
if active_features:
|
|
182
|
+
feature_list = "\n".join(
|
|
183
|
+
[f"- **{f['id']}**: {f['title']}" for f in active_features[:3]]
|
|
184
|
+
)
|
|
185
|
+
context_str = f"""## Active Features
|
|
186
|
+
|
|
187
|
+
{feature_list}
|
|
188
|
+
|
|
189
|
+
Activity will be attributed to these features based on file patterns and keywords.
|
|
190
|
+
|
|
191
|
+
**To view all work and progress:** `htmlgraph snapshot --summary`"""
|
|
192
|
+
output["hookSpecificOutput"]["sessionFeatureContext"] = context_str
|
|
193
|
+
context.log("info", f"Loaded {len(active_features)} active features")
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
context.log("warning", f"Could not load features: {e}")
|
|
197
|
+
|
|
198
|
+
# Check version status
|
|
199
|
+
try:
|
|
200
|
+
version_info = check_version_status()
|
|
201
|
+
if version_info and version_info.get("is_outdated"):
|
|
202
|
+
output["hookSpecificOutput"]["versionInfo"] = version_info
|
|
203
|
+
context.log(
|
|
204
|
+
"info",
|
|
205
|
+
f"Update available: {version_info.get('installed')} → {version_info.get('latest')}",
|
|
206
|
+
)
|
|
207
|
+
except Exception as e:
|
|
208
|
+
context.log("debug", f"Could not check version: {e}")
|
|
209
|
+
|
|
210
|
+
# Build concurrent session context
|
|
211
|
+
try:
|
|
212
|
+
from htmlgraph.hooks.concurrent_sessions import (
|
|
213
|
+
format_concurrent_sessions_markdown,
|
|
214
|
+
format_recent_work_markdown,
|
|
215
|
+
get_concurrent_sessions,
|
|
216
|
+
get_recent_completed_sessions,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
db = context.database
|
|
220
|
+
|
|
221
|
+
# Get concurrent sessions (other active windows)
|
|
222
|
+
concurrent = get_concurrent_sessions(db, context.session_id, minutes=30)
|
|
223
|
+
concurrent_md = format_concurrent_sessions_markdown(concurrent)
|
|
224
|
+
|
|
225
|
+
# Get recent completed work
|
|
226
|
+
recent = get_recent_completed_sessions(db, hours=24, limit=5)
|
|
227
|
+
recent_md = format_recent_work_markdown(recent)
|
|
228
|
+
|
|
229
|
+
# Build session context
|
|
230
|
+
session_context = ""
|
|
231
|
+
if concurrent_md:
|
|
232
|
+
session_context += concurrent_md + "\n"
|
|
233
|
+
if recent_md:
|
|
234
|
+
session_context += recent_md + "\n"
|
|
235
|
+
|
|
236
|
+
if session_context:
|
|
237
|
+
output["hookSpecificOutput"]["sessionContext"] = session_context.strip()
|
|
238
|
+
context.log(
|
|
239
|
+
"info",
|
|
240
|
+
f"Injected context: {len(concurrent)} concurrent, {len(recent)} recent",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
except ImportError:
|
|
244
|
+
context.log("debug", "Concurrent session module not available")
|
|
245
|
+
except Exception as e:
|
|
246
|
+
context.log("warning", f"Failed to get concurrent session context: {e}")
|
|
247
|
+
|
|
248
|
+
# Update session with user's current query (if available from hook input)
|
|
249
|
+
try:
|
|
250
|
+
user_query = context.hook_input.get("prompt", "")
|
|
251
|
+
if user_query and session:
|
|
252
|
+
context.session_manager.track_activity(
|
|
253
|
+
session_id=session.id,
|
|
254
|
+
tool="UserQuery",
|
|
255
|
+
summary=user_query[:100],
|
|
256
|
+
payload={"query_length": len(user_query)},
|
|
257
|
+
)
|
|
258
|
+
except Exception as e:
|
|
259
|
+
context.log("warning", f"Failed to update session activity: {e}")
|
|
260
|
+
|
|
261
|
+
return output
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def handle_session_end(context: HookContext) -> dict[str, Any]:
|
|
265
|
+
"""
|
|
266
|
+
Close session gracefully and record final metrics.
|
|
267
|
+
|
|
268
|
+
Performs session end operations:
|
|
269
|
+
- Captures handoff notes if provided
|
|
270
|
+
- Links transcript if available
|
|
271
|
+
- Records session end event
|
|
272
|
+
- Cleans up temporary state files
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
context: HookContext with project and graph directory information
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
dict with:
|
|
279
|
+
{
|
|
280
|
+
"continue": True,
|
|
281
|
+
"status": "success" | "partial" | "error"
|
|
282
|
+
}
|
|
283
|
+
"""
|
|
284
|
+
output: dict[str, Any] = {
|
|
285
|
+
"continue": True,
|
|
286
|
+
"status": "success",
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
session = context.session_manager.get_active_session()
|
|
291
|
+
if not session:
|
|
292
|
+
context.log("debug", "No active session to close")
|
|
293
|
+
else:
|
|
294
|
+
# Capture handoff context if provided
|
|
295
|
+
handoff_notes = context.hook_input.get("handoff_notes") or os.environ.get(
|
|
296
|
+
"HTMLGRAPH_HANDOFF_NOTES"
|
|
297
|
+
)
|
|
298
|
+
recommended_next = context.hook_input.get(
|
|
299
|
+
"recommended_next"
|
|
300
|
+
) or os.environ.get("HTMLGRAPH_HANDOFF_RECOMMEND")
|
|
301
|
+
blockers_raw = context.hook_input.get("blockers") or os.environ.get(
|
|
302
|
+
"HTMLGRAPH_HANDOFF_BLOCKERS"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
blockers = None
|
|
306
|
+
if isinstance(blockers_raw, str):
|
|
307
|
+
blockers = [b.strip() for b in blockers_raw.split(",") if b.strip()]
|
|
308
|
+
elif isinstance(blockers_raw, list):
|
|
309
|
+
blockers = [str(b).strip() for b in blockers_raw if str(b).strip()]
|
|
310
|
+
|
|
311
|
+
if handoff_notes or recommended_next or blockers:
|
|
312
|
+
try:
|
|
313
|
+
context.session_manager.set_session_handoff(
|
|
314
|
+
session_id=session.id,
|
|
315
|
+
handoff_notes=handoff_notes,
|
|
316
|
+
recommended_next=recommended_next,
|
|
317
|
+
blockers=blockers,
|
|
318
|
+
)
|
|
319
|
+
context.log("info", "Session handoff recorded")
|
|
320
|
+
except Exception as e:
|
|
321
|
+
context.log("warning", f"Could not set handoff: {e}")
|
|
322
|
+
output["status"] = "partial"
|
|
323
|
+
|
|
324
|
+
# Link transcript if external session ID provided
|
|
325
|
+
external_session_id = context.hook_input.get(
|
|
326
|
+
"session_id"
|
|
327
|
+
) or os.environ.get("CLAUDE_SESSION_ID")
|
|
328
|
+
if external_session_id:
|
|
329
|
+
try:
|
|
330
|
+
from htmlgraph.transcript import TranscriptReader
|
|
331
|
+
|
|
332
|
+
reader = TranscriptReader()
|
|
333
|
+
transcript = reader.read_session(external_session_id)
|
|
334
|
+
if transcript:
|
|
335
|
+
context.session_manager.link_transcript(
|
|
336
|
+
session_id=session.id,
|
|
337
|
+
transcript_id=external_session_id,
|
|
338
|
+
transcript_path=str(transcript.path),
|
|
339
|
+
git_branch=transcript.git_branch
|
|
340
|
+
if hasattr(transcript, "git_branch")
|
|
341
|
+
else None,
|
|
342
|
+
)
|
|
343
|
+
context.log("info", "Transcript linked to session")
|
|
344
|
+
except ImportError:
|
|
345
|
+
context.log("debug", "Transcript reader not available")
|
|
346
|
+
except Exception as e:
|
|
347
|
+
context.log("warning", f"Could not link transcript: {e}")
|
|
348
|
+
output["status"] = "partial"
|
|
349
|
+
|
|
350
|
+
# Record session end activity
|
|
351
|
+
try:
|
|
352
|
+
context.session_manager.track_activity(
|
|
353
|
+
session_id=session.id,
|
|
354
|
+
tool="SessionEnd",
|
|
355
|
+
summary="Session ended",
|
|
356
|
+
)
|
|
357
|
+
except Exception as e:
|
|
358
|
+
context.log("warning", f"Could not track session end: {e}")
|
|
359
|
+
output["status"] = "partial"
|
|
360
|
+
|
|
361
|
+
context.log("info", f"Session closed: {session.id}")
|
|
362
|
+
|
|
363
|
+
except ImportError:
|
|
364
|
+
context.log("error", "SessionManager not available")
|
|
365
|
+
output["status"] = "error"
|
|
366
|
+
except Exception as e:
|
|
367
|
+
context.log("error", f"Failed to close session: {e}")
|
|
368
|
+
output["status"] = "error"
|
|
369
|
+
|
|
370
|
+
# Always cleanup temp files
|
|
371
|
+
try:
|
|
372
|
+
_cleanup_temp_files(context.graph_dir)
|
|
373
|
+
except Exception as e:
|
|
374
|
+
context.log("warning", f"Could not cleanup temp files: {e}")
|
|
375
|
+
|
|
376
|
+
return output
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def record_user_query_event(context: HookContext, prompt: str) -> str | None:
|
|
380
|
+
"""
|
|
381
|
+
Create UserQuery event in database.
|
|
382
|
+
|
|
383
|
+
Records a user query prompt as an event in the database for later
|
|
384
|
+
reference by tool calls in the same conversation turn.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
context: HookContext with project and graph directory information
|
|
388
|
+
prompt: The user query prompt text
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
event_id if successful, None otherwise
|
|
392
|
+
|
|
393
|
+
Note:
|
|
394
|
+
- Event ID is stored for parent-child linking of subsequent tool calls
|
|
395
|
+
- Events expire after 10 minutes (conversation turn boundary)
|
|
396
|
+
- Safe to call even if database unavailable (graceful degradation)
|
|
397
|
+
"""
|
|
398
|
+
try:
|
|
399
|
+
from htmlgraph.ids import generate_id
|
|
400
|
+
|
|
401
|
+
db = context.database
|
|
402
|
+
event_id = generate_id("event")
|
|
403
|
+
|
|
404
|
+
# Preview for logging
|
|
405
|
+
preview = prompt[:100].replace("\n", " ")
|
|
406
|
+
if len(prompt) > 100:
|
|
407
|
+
preview += "..."
|
|
408
|
+
|
|
409
|
+
# Insert UserQuery event
|
|
410
|
+
success = db.insert_event(
|
|
411
|
+
event_id=event_id,
|
|
412
|
+
agent_id=context.agent_id,
|
|
413
|
+
event_type="user_query",
|
|
414
|
+
session_id=context.session_id,
|
|
415
|
+
tool_name="UserQuery",
|
|
416
|
+
input_summary=preview,
|
|
417
|
+
output_summary="Query recorded",
|
|
418
|
+
context={"full_prompt_length": len(prompt)},
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if success:
|
|
422
|
+
context.log("info", f"Recorded UserQuery event: {event_id}")
|
|
423
|
+
return event_id
|
|
424
|
+
else:
|
|
425
|
+
context.log("warning", "Failed to insert UserQuery event")
|
|
426
|
+
return None
|
|
427
|
+
|
|
428
|
+
except ImportError:
|
|
429
|
+
context.log("debug", "Database not available for UserQuery event")
|
|
430
|
+
return None
|
|
431
|
+
except Exception as e:
|
|
432
|
+
context.log("error", f"Failed to record UserQuery event: {e}")
|
|
433
|
+
return None
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def check_version_status() -> dict | None:
|
|
437
|
+
"""
|
|
438
|
+
Check if HtmlGraph has updates available.
|
|
439
|
+
|
|
440
|
+
Compares installed version with latest version on PyPI.
|
|
441
|
+
Attempts multiple methods to get version information:
|
|
442
|
+
1. import htmlgraph and check __version__
|
|
443
|
+
2. pip show htmlgraph
|
|
444
|
+
3. PyPI JSON API (requires network)
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
dict with version info if outdated:
|
|
448
|
+
{
|
|
449
|
+
"installed": "0.9.0",
|
|
450
|
+
"latest": "0.9.1",
|
|
451
|
+
"is_outdated": True
|
|
452
|
+
}
|
|
453
|
+
None if versions match or cannot be determined
|
|
454
|
+
|
|
455
|
+
Note:
|
|
456
|
+
- Never blocks on network errors (5 second timeout)
|
|
457
|
+
- Gracefully degrades if methods unavailable
|
|
458
|
+
- Safe for use in hooks (catches all exceptions)
|
|
459
|
+
"""
|
|
460
|
+
try:
|
|
461
|
+
installed_version = _get_installed_version()
|
|
462
|
+
latest_version = _get_latest_pypi_version()
|
|
463
|
+
|
|
464
|
+
if not (installed_version and latest_version):
|
|
465
|
+
return None
|
|
466
|
+
|
|
467
|
+
if installed_version == latest_version:
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
# Compare versions
|
|
471
|
+
is_outdated = _compare_versions(installed_version, latest_version)
|
|
472
|
+
if is_outdated:
|
|
473
|
+
return {
|
|
474
|
+
"installed": installed_version,
|
|
475
|
+
"latest": latest_version,
|
|
476
|
+
"is_outdated": True,
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return None
|
|
480
|
+
|
|
481
|
+
except Exception:
|
|
482
|
+
return None
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
# ============================================================================
|
|
486
|
+
# Private Helper Functions
|
|
487
|
+
# ============================================================================
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _get_head_commit(project_dir: str) -> str | None:
|
|
491
|
+
"""Get current HEAD commit hash (short form)."""
|
|
492
|
+
try:
|
|
493
|
+
result = subprocess.run(
|
|
494
|
+
["git", "rev-parse", "--short", "HEAD"],
|
|
495
|
+
capture_output=True,
|
|
496
|
+
text=True,
|
|
497
|
+
cwd=project_dir,
|
|
498
|
+
timeout=5,
|
|
499
|
+
)
|
|
500
|
+
if result.returncode == 0:
|
|
501
|
+
return result.stdout.strip()
|
|
502
|
+
except Exception:
|
|
503
|
+
pass
|
|
504
|
+
return None
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _load_features(graph_dir: Path) -> list[dict]:
|
|
508
|
+
"""Load all features as dicts."""
|
|
509
|
+
try:
|
|
510
|
+
from htmlgraph.converter import node_to_dict # type: ignore[import]
|
|
511
|
+
from htmlgraph.graph import HtmlGraph
|
|
512
|
+
|
|
513
|
+
features_dir = graph_dir / "features"
|
|
514
|
+
if not features_dir.exists():
|
|
515
|
+
return []
|
|
516
|
+
|
|
517
|
+
graph = HtmlGraph(features_dir, auto_load=True)
|
|
518
|
+
return [node_to_dict(node) for node in graph.nodes.values()]
|
|
519
|
+
|
|
520
|
+
except Exception:
|
|
521
|
+
return []
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def _get_installed_version() -> str | None:
|
|
525
|
+
"""Get installed htmlgraph version."""
|
|
526
|
+
# Method 1: Import and check __version__
|
|
527
|
+
try:
|
|
528
|
+
import htmlgraph
|
|
529
|
+
|
|
530
|
+
return htmlgraph.__version__
|
|
531
|
+
except Exception:
|
|
532
|
+
pass
|
|
533
|
+
|
|
534
|
+
# Method 2: pip show
|
|
535
|
+
try:
|
|
536
|
+
result = subprocess.run(
|
|
537
|
+
["pip", "show", "htmlgraph"],
|
|
538
|
+
capture_output=True,
|
|
539
|
+
text=True,
|
|
540
|
+
timeout=10,
|
|
541
|
+
)
|
|
542
|
+
if result.returncode == 0:
|
|
543
|
+
for line in result.stdout.splitlines():
|
|
544
|
+
if line.startswith("Version:"):
|
|
545
|
+
return line.split(":", 1)[1].strip()
|
|
546
|
+
except Exception:
|
|
547
|
+
pass
|
|
548
|
+
|
|
549
|
+
return None
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def _get_latest_pypi_version() -> str | None:
|
|
553
|
+
"""Get latest htmlgraph version from PyPI."""
|
|
554
|
+
try:
|
|
555
|
+
import urllib.request
|
|
556
|
+
|
|
557
|
+
req = urllib.request.Request(
|
|
558
|
+
"https://pypi.org/pypi/htmlgraph/json",
|
|
559
|
+
headers={
|
|
560
|
+
"Accept": "application/json",
|
|
561
|
+
"User-Agent": "htmlgraph-version-check",
|
|
562
|
+
},
|
|
563
|
+
)
|
|
564
|
+
with urllib.request.urlopen(req, timeout=5) as response:
|
|
565
|
+
data = json.loads(response.read().decode())
|
|
566
|
+
version: str | None = data.get("info", {}).get("version")
|
|
567
|
+
return version
|
|
568
|
+
except Exception:
|
|
569
|
+
return None
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _compare_versions(installed: str, latest: str) -> bool:
|
|
573
|
+
"""
|
|
574
|
+
Check if installed version is older than latest.
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
installed: Installed version string
|
|
578
|
+
latest: Latest version string
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
True if installed < latest, False otherwise
|
|
582
|
+
|
|
583
|
+
Note:
|
|
584
|
+
- Uses semantic versioning comparison
|
|
585
|
+
- Falls back to string comparison for non-semver versions
|
|
586
|
+
"""
|
|
587
|
+
try:
|
|
588
|
+
# Try semantic version comparison
|
|
589
|
+
installed_parts = [int(x) for x in installed.split(".")]
|
|
590
|
+
latest_parts = [int(x) for x in latest.split(".")]
|
|
591
|
+
return installed_parts < latest_parts
|
|
592
|
+
except (ValueError, IndexError):
|
|
593
|
+
# Fallback to string comparison
|
|
594
|
+
return installed != latest
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def _cleanup_temp_files(graph_dir: Path) -> None:
|
|
598
|
+
"""
|
|
599
|
+
Clean up temporary state files after session end.
|
|
600
|
+
|
|
601
|
+
Removes session-scoped temporary files that are no longer needed.
|
|
602
|
+
Safe to call even if files don't exist (idempotent).
|
|
603
|
+
|
|
604
|
+
Args:
|
|
605
|
+
graph_dir: Path to .htmlgraph directory
|
|
606
|
+
"""
|
|
607
|
+
temp_patterns = [
|
|
608
|
+
"parent-activity.json",
|
|
609
|
+
"user-query-event-*.json",
|
|
610
|
+
]
|
|
611
|
+
|
|
612
|
+
for pattern in temp_patterns:
|
|
613
|
+
if "*" in pattern:
|
|
614
|
+
# Handle glob patterns
|
|
615
|
+
import glob
|
|
616
|
+
|
|
617
|
+
for path in glob.glob(str(graph_dir / pattern)):
|
|
618
|
+
try:
|
|
619
|
+
Path(path).unlink()
|
|
620
|
+
logger.debug(f"Cleaned up: {path}")
|
|
621
|
+
except Exception as e:
|
|
622
|
+
logger.debug(f"Could not clean up {path}: {e}")
|
|
623
|
+
else:
|
|
624
|
+
# Handle single file
|
|
625
|
+
file_path: Path = graph_dir / pattern
|
|
626
|
+
try:
|
|
627
|
+
if file_path.exists():
|
|
628
|
+
file_path.unlink()
|
|
629
|
+
logger.debug(f"Cleaned up: {file_path}")
|
|
630
|
+
except Exception as e:
|
|
631
|
+
logger.debug(f"Could not clean up {file_path}: {e}")
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
__all__ = [
|
|
635
|
+
"init_or_get_session",
|
|
636
|
+
"handle_session_start",
|
|
637
|
+
"handle_session_end",
|
|
638
|
+
"record_user_query_event",
|
|
639
|
+
"check_version_status",
|
|
640
|
+
]
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def main() -> None:
|
|
644
|
+
"""Hook entry point for SessionEnd hook."""
|
|
645
|
+
import json
|
|
646
|
+
import os
|
|
647
|
+
import sys
|
|
648
|
+
|
|
649
|
+
# Check if tracking is disabled
|
|
650
|
+
if os.environ.get("HTMLGRAPH_DISABLE_TRACKING") == "1":
|
|
651
|
+
print(json.dumps({"continue": True}))
|
|
652
|
+
sys.exit(0)
|
|
653
|
+
|
|
654
|
+
try:
|
|
655
|
+
hook_input = json.load(sys.stdin)
|
|
656
|
+
except json.JSONDecodeError:
|
|
657
|
+
hook_input = {}
|
|
658
|
+
|
|
659
|
+
# Create context from hook input
|
|
660
|
+
from htmlgraph.hooks.context import HookContext
|
|
661
|
+
|
|
662
|
+
context = HookContext.from_input(hook_input)
|
|
663
|
+
|
|
664
|
+
# Handle session end
|
|
665
|
+
response = handle_session_end(context)
|
|
666
|
+
|
|
667
|
+
# Output JSON response
|
|
668
|
+
print(json.dumps(response))
|