htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +67 -27
- htmlgraph/analytics_index.py +53 -20
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/deploy.py +26 -27
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hook Execution Context Manager.
|
|
3
|
+
|
|
4
|
+
Manages hook execution context including lazy-loading of expensive resources
|
|
5
|
+
(database, session manager) to minimize initialization overhead.
|
|
6
|
+
|
|
7
|
+
This module provides a centralized context object that hooks can use to:
|
|
8
|
+
- Access the graph directory and project directory
|
|
9
|
+
- Retrieve session information
|
|
10
|
+
- Access the database for event recording
|
|
11
|
+
- Perform unified logging
|
|
12
|
+
|
|
13
|
+
Key Design Principles:
|
|
14
|
+
- Lazy-loading: Expensive resources (DB, SessionManager) are only loaded on first access
|
|
15
|
+
- Resource cleanup: Context properly closes resources when done
|
|
16
|
+
- Type safety: Full type hints for all public methods and properties
|
|
17
|
+
- Error handling: Graceful degradation if resources fail to initialize
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class HookContext:
|
|
31
|
+
"""
|
|
32
|
+
Hook execution context with lazy-loaded resources.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
project_dir: Absolute path to project root directory
|
|
36
|
+
graph_dir: Path to .htmlgraph directory for tracking data
|
|
37
|
+
session_id: Unique session identifier for this execution
|
|
38
|
+
agent_id: Agent/tool that's executing (e.g., 'claude-code', 'codex')
|
|
39
|
+
hook_input: Raw hook input data from Claude Code
|
|
40
|
+
model_name: Specific Claude model name (e.g., 'claude-haiku', 'claude-opus', 'claude-sonnet')
|
|
41
|
+
_session_manager: Cached SessionManager instance (lazy-loaded)
|
|
42
|
+
_database: Cached HtmlGraphDB instance (lazy-loaded)
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
project_dir: str
|
|
46
|
+
graph_dir: Path
|
|
47
|
+
session_id: str
|
|
48
|
+
agent_id: str
|
|
49
|
+
hook_input: dict[str, Any]
|
|
50
|
+
model_name: str | None = field(default=None, repr=False)
|
|
51
|
+
_session_manager: Any | None = field(default=None, repr=False)
|
|
52
|
+
_database: Any | None = field(default=None, repr=False)
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_input(cls, hook_input: dict[str, Any]) -> "HookContext":
|
|
56
|
+
"""
|
|
57
|
+
Create HookContext from raw hook input.
|
|
58
|
+
|
|
59
|
+
Performs automatic environment resolution:
|
|
60
|
+
- Extracts session_id from hook_input
|
|
61
|
+
- Detects agent_id from environment or hook_input
|
|
62
|
+
- Detects model_name (e.g., claude-haiku, claude-opus, claude-sonnet)
|
|
63
|
+
- Resolves project directory via bootstrap
|
|
64
|
+
- Initializes graph directory
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
hook_input: Raw hook input dict from Claude Code hook system
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Initialized HookContext instance
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
ImportError: If bootstrap module cannot be imported
|
|
74
|
+
OSError: If graph directory cannot be created
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
```python
|
|
78
|
+
hook_input = {
|
|
79
|
+
'session_id': 'sess-abc123',
|
|
80
|
+
'type': 'pretooluse',
|
|
81
|
+
'tool_name': 'Edit',
|
|
82
|
+
...
|
|
83
|
+
}
|
|
84
|
+
context = HookContext.from_input(hook_input)
|
|
85
|
+
logger.info(f"Session: {context.session_id}, Agent: {context.agent_id}, Model: {context.model_name}")
|
|
86
|
+
```
|
|
87
|
+
"""
|
|
88
|
+
# Import bootstrap locally to avoid circular imports
|
|
89
|
+
from htmlgraph.hooks.bootstrap import (
|
|
90
|
+
get_graph_dir,
|
|
91
|
+
resolve_project_dir,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Resolve project directory first
|
|
95
|
+
project_dir = resolve_project_dir()
|
|
96
|
+
graph_dir = get_graph_dir(project_dir)
|
|
97
|
+
|
|
98
|
+
# Extract session ID with multiple fallbacks
|
|
99
|
+
# Priority order:
|
|
100
|
+
# 1. hook_input["session_id"] (if Claude Code passes it)
|
|
101
|
+
# 2. hook_input["sessionId"] (camelCase variant)
|
|
102
|
+
# 3. HTMLGRAPH_SESSION_ID environment variable
|
|
103
|
+
# 4. CLAUDE_SESSION_ID environment variable
|
|
104
|
+
# 5. Most recent active session from database (NEW)
|
|
105
|
+
# 6. "unknown" as last resort
|
|
106
|
+
#
|
|
107
|
+
# NOTE: We intentionally do NOT use SessionManager.get_active_session()
|
|
108
|
+
# as a fallback because the "active session" is stored in a global file
|
|
109
|
+
# (.htmlgraph/session.json) that's shared across all Claude windows.
|
|
110
|
+
# Using it would cause cross-window event contamination where tool calls
|
|
111
|
+
# from Window B get linked to UserQuery events from Window A.
|
|
112
|
+
#
|
|
113
|
+
# However, we DO query the database by status='active' and created_at,
|
|
114
|
+
# which is different because it retrieves the most recent session that
|
|
115
|
+
# was explicitly marked as active (e.g., by SessionStart hook), without
|
|
116
|
+
# relying on a shared global agent state file.
|
|
117
|
+
session_id = (
|
|
118
|
+
hook_input.get("session_id")
|
|
119
|
+
or hook_input.get("sessionId")
|
|
120
|
+
or os.environ.get("HTMLGRAPH_SESSION_ID")
|
|
121
|
+
or os.environ.get("CLAUDE_SESSION_ID")
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Fallback: Query database for session with most recent UserQuery event
|
|
125
|
+
# This solves the issue where PostToolUse hooks don't receive session_id
|
|
126
|
+
# in hook_input. UserPromptSubmit hooks DO receive it and create UserQuery
|
|
127
|
+
# events with the correct session_id, so we use that as the source of truth.
|
|
128
|
+
if not session_id:
|
|
129
|
+
db_path = graph_dir / "htmlgraph.db"
|
|
130
|
+
if db_path.exists():
|
|
131
|
+
try:
|
|
132
|
+
import sqlite3
|
|
133
|
+
|
|
134
|
+
conn = sqlite3.connect(str(db_path), timeout=1.0)
|
|
135
|
+
cursor = conn.cursor()
|
|
136
|
+
cursor.execute("""
|
|
137
|
+
SELECT session_id FROM agent_events
|
|
138
|
+
WHERE tool_name = 'UserQuery'
|
|
139
|
+
ORDER BY timestamp DESC
|
|
140
|
+
LIMIT 1
|
|
141
|
+
""")
|
|
142
|
+
row = cursor.fetchone()
|
|
143
|
+
conn.close()
|
|
144
|
+
if row:
|
|
145
|
+
session_id = row[0]
|
|
146
|
+
logger.info(f"Resolved session_id from database: {session_id}")
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.warning(f"Failed to query active session from database: {e}")
|
|
149
|
+
|
|
150
|
+
# Final fallback to "unknown" if database query fails
|
|
151
|
+
if not session_id:
|
|
152
|
+
session_id = "unknown"
|
|
153
|
+
logger.warning(
|
|
154
|
+
"Could not resolve session_id from hook_input, environment, or database. "
|
|
155
|
+
"Events will not be linked to parent UserQuery. "
|
|
156
|
+
"For multi-window support, set HTMLGRAPH_SESSION_ID env var."
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Detect agent ID (priority order)
|
|
160
|
+
# 1. Explicit agent_id in hook input
|
|
161
|
+
# 2. HTMLGRAPH_AGENT_ID environment variable
|
|
162
|
+
# 3. CLAUDE_AGENT_NICKNAME environment variable (Claude Code)
|
|
163
|
+
# 4. Default to 'unknown'
|
|
164
|
+
agent_id = (
|
|
165
|
+
hook_input.get("agent_id")
|
|
166
|
+
or os.environ.get("HTMLGRAPH_AGENT_ID")
|
|
167
|
+
or os.environ.get("CLAUDE_AGENT_NICKNAME", "unknown")
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Detect model name (priority order)
|
|
171
|
+
# 1. Explicit model_name in hook input
|
|
172
|
+
# 2. CLAUDE_MODEL environment variable
|
|
173
|
+
# 3. HTMLGRAPH_MODEL environment variable
|
|
174
|
+
# 4. Status line cache (from ~/.cache/claude-code/status-{session_id}.json)
|
|
175
|
+
# 5. None (not available)
|
|
176
|
+
model_name = (
|
|
177
|
+
hook_input.get("model_name")
|
|
178
|
+
or hook_input.get("model")
|
|
179
|
+
or os.environ.get("CLAUDE_MODEL")
|
|
180
|
+
or os.environ.get("HTMLGRAPH_MODEL")
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Fallback: Try status line cache if model not detected yet
|
|
184
|
+
if not model_name and session_id and session_id != "unknown":
|
|
185
|
+
from htmlgraph.hooks.event_tracker import get_model_from_status_cache
|
|
186
|
+
|
|
187
|
+
model_name = get_model_from_status_cache(session_id)
|
|
188
|
+
|
|
189
|
+
logger.info(
|
|
190
|
+
f"Initializing hook context: session={session_id}, "
|
|
191
|
+
f"agent={agent_id}, model={model_name}, project={project_dir}"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return cls(
|
|
195
|
+
project_dir=project_dir,
|
|
196
|
+
graph_dir=graph_dir,
|
|
197
|
+
session_id=session_id,
|
|
198
|
+
agent_id=agent_id,
|
|
199
|
+
hook_input=hook_input,
|
|
200
|
+
model_name=model_name,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def session_manager(self) -> Any:
|
|
205
|
+
"""
|
|
206
|
+
Lazy-load and cache SessionManager instance.
|
|
207
|
+
|
|
208
|
+
Importing SessionManager is expensive (thousands of file system operations
|
|
209
|
+
for graph initialization), so we defer until first access.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
SessionManager instance for session tracking and activity attribution
|
|
213
|
+
|
|
214
|
+
Raises:
|
|
215
|
+
ImportError: If SessionManager cannot be imported
|
|
216
|
+
Exception: If SessionManager initialization fails
|
|
217
|
+
|
|
218
|
+
Note:
|
|
219
|
+
SessionManager is cached after first access. Multiple accesses
|
|
220
|
+
return the same instance.
|
|
221
|
+
"""
|
|
222
|
+
if self._session_manager is not None:
|
|
223
|
+
return self._session_manager
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
from htmlgraph.session_manager import SessionManager
|
|
227
|
+
|
|
228
|
+
logger.debug(f"Loading SessionManager for {self.graph_dir}")
|
|
229
|
+
self._session_manager = SessionManager(graph_dir=self.graph_dir)
|
|
230
|
+
logger.info("SessionManager loaded successfully")
|
|
231
|
+
return self._session_manager
|
|
232
|
+
except ImportError as e:
|
|
233
|
+
logger.error(f"Failed to import SessionManager: {e}")
|
|
234
|
+
raise
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.error(f"Failed to initialize SessionManager: {e}")
|
|
237
|
+
raise
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def database(self) -> Any:
|
|
241
|
+
"""
|
|
242
|
+
Lazy-load and cache HtmlGraphDB instance.
|
|
243
|
+
|
|
244
|
+
Database access is needed for event recording, but we defer initialization
|
|
245
|
+
until first access to minimize startup overhead.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
HtmlGraphDB instance for recording events and features
|
|
249
|
+
|
|
250
|
+
Raises:
|
|
251
|
+
ImportError: If HtmlGraphDB cannot be imported
|
|
252
|
+
Exception: If database connection fails
|
|
253
|
+
|
|
254
|
+
Note:
|
|
255
|
+
Database connection is cached after first access. Multiple accesses
|
|
256
|
+
return the same instance.
|
|
257
|
+
"""
|
|
258
|
+
if self._database is not None:
|
|
259
|
+
return self._database
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
from htmlgraph.db.schema import HtmlGraphDB
|
|
263
|
+
|
|
264
|
+
db_path = self.graph_dir / "htmlgraph.db"
|
|
265
|
+
logger.debug(f"Loading HtmlGraphDB at {db_path}")
|
|
266
|
+
self._database = HtmlGraphDB(str(db_path))
|
|
267
|
+
logger.info("HtmlGraphDB loaded successfully")
|
|
268
|
+
return self._database
|
|
269
|
+
except ImportError as e:
|
|
270
|
+
logger.error(f"Failed to import HtmlGraphDB: {e}")
|
|
271
|
+
raise
|
|
272
|
+
except Exception as e:
|
|
273
|
+
logger.error(f"Failed to initialize HtmlGraphDB: {e}")
|
|
274
|
+
raise
|
|
275
|
+
|
|
276
|
+
def close(self) -> None:
|
|
277
|
+
"""
|
|
278
|
+
Clean up and close all resources gracefully.
|
|
279
|
+
|
|
280
|
+
Closes database connections and session manager resources.
|
|
281
|
+
Safe to call multiple times (idempotent).
|
|
282
|
+
|
|
283
|
+
This should be called in a finally block to ensure cleanup:
|
|
284
|
+
|
|
285
|
+
Example:
|
|
286
|
+
```python
|
|
287
|
+
context = HookContext.from_input(hook_input)
|
|
288
|
+
try:
|
|
289
|
+
# Use context
|
|
290
|
+
context.session_manager.track_activity(...)
|
|
291
|
+
finally:
|
|
292
|
+
context.close() # Always cleanup
|
|
293
|
+
```
|
|
294
|
+
"""
|
|
295
|
+
# Close database if loaded
|
|
296
|
+
if self._database is not None:
|
|
297
|
+
try:
|
|
298
|
+
logger.debug("Closing database connection")
|
|
299
|
+
self._database.close()
|
|
300
|
+
self._database = None
|
|
301
|
+
logger.info("Database closed successfully")
|
|
302
|
+
except Exception as e:
|
|
303
|
+
logger.warning(f"Error closing database: {e}")
|
|
304
|
+
|
|
305
|
+
# Close session manager if loaded
|
|
306
|
+
if self._session_manager is not None:
|
|
307
|
+
try:
|
|
308
|
+
logger.debug("Closing session manager")
|
|
309
|
+
# SessionManager doesn't currently have a close method,
|
|
310
|
+
# but we keep this for future resource management
|
|
311
|
+
self._session_manager = None
|
|
312
|
+
logger.info("Session manager cleaned up")
|
|
313
|
+
except Exception as e:
|
|
314
|
+
logger.warning(f"Error closing session manager: {e}")
|
|
315
|
+
|
|
316
|
+
def log(self, level: str, message: str) -> None:
|
|
317
|
+
"""
|
|
318
|
+
Unified logging for hooks.
|
|
319
|
+
|
|
320
|
+
Provides consistent logging across all hook modules with context
|
|
321
|
+
information (session_id, agent_id, project_dir).
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
level: Log level as string ('debug', 'info', 'warning', 'error', 'critical')
|
|
325
|
+
message: Message to log
|
|
326
|
+
|
|
327
|
+
Example:
|
|
328
|
+
```python
|
|
329
|
+
context.log('info', 'Processing user query')
|
|
330
|
+
context.log('error', f'Failed to track activity: {error}')
|
|
331
|
+
```
|
|
332
|
+
"""
|
|
333
|
+
log_func = getattr(logger, level.lower(), logger.info)
|
|
334
|
+
|
|
335
|
+
# Prefix message with context for better debugging
|
|
336
|
+
context_msg = f"[{self.session_id[:8]}][{self.agent_id}] {message}"
|
|
337
|
+
log_func(context_msg)
|
|
338
|
+
|
|
339
|
+
def __enter__(self) -> "HookContext":
|
|
340
|
+
"""Context manager entry."""
|
|
341
|
+
return self
|
|
342
|
+
|
|
343
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
344
|
+
"""Context manager exit with resource cleanup."""
|
|
345
|
+
self.close()
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
__all__ = [
|
|
349
|
+
"HookContext",
|
|
350
|
+
]
|