htmlgraph 0.24.2__py3-none-any.whl → 0.26.1__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.
Files changed (112) hide show
  1. htmlgraph/__init__.py +20 -1
  2. htmlgraph/agent_detection.py +26 -10
  3. htmlgraph/analytics/cross_session.py +4 -3
  4. htmlgraph/analytics/work_type.py +52 -16
  5. htmlgraph/analytics_index.py +51 -19
  6. htmlgraph/api/__init__.py +3 -0
  7. htmlgraph/api/main.py +2263 -0
  8. htmlgraph/api/static/htmx.min.js +1 -0
  9. htmlgraph/api/static/style-redesign.css +1344 -0
  10. htmlgraph/api/static/style.css +1079 -0
  11. htmlgraph/api/templates/dashboard-redesign.html +812 -0
  12. htmlgraph/api/templates/dashboard.html +794 -0
  13. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  14. htmlgraph/api/templates/partials/activity-feed.html +1020 -0
  15. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  16. htmlgraph/api/templates/partials/agents.html +317 -0
  17. htmlgraph/api/templates/partials/event-traces.html +373 -0
  18. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  19. htmlgraph/api/templates/partials/features.html +509 -0
  20. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  21. htmlgraph/api/templates/partials/metrics.html +346 -0
  22. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  23. htmlgraph/api/templates/partials/orchestration.html +163 -0
  24. htmlgraph/api/templates/partials/spawners.html +375 -0
  25. htmlgraph/atomic_ops.py +560 -0
  26. htmlgraph/builders/base.py +55 -1
  27. htmlgraph/builders/bug.py +17 -2
  28. htmlgraph/builders/chore.py +17 -2
  29. htmlgraph/builders/epic.py +17 -2
  30. htmlgraph/builders/feature.py +25 -2
  31. htmlgraph/builders/phase.py +17 -2
  32. htmlgraph/builders/spike.py +27 -2
  33. htmlgraph/builders/track.py +14 -0
  34. htmlgraph/cigs/__init__.py +4 -0
  35. htmlgraph/cigs/reporter.py +818 -0
  36. htmlgraph/cli.py +1427 -401
  37. htmlgraph/cli_commands/__init__.py +1 -0
  38. htmlgraph/cli_commands/feature.py +195 -0
  39. htmlgraph/cli_framework.py +115 -0
  40. htmlgraph/collections/__init__.py +2 -0
  41. htmlgraph/collections/base.py +21 -0
  42. htmlgraph/collections/session.py +189 -0
  43. htmlgraph/collections/spike.py +7 -1
  44. htmlgraph/collections/task_delegation.py +236 -0
  45. htmlgraph/collections/traces.py +482 -0
  46. htmlgraph/config.py +113 -0
  47. htmlgraph/converter.py +41 -0
  48. htmlgraph/cost_analysis/__init__.py +5 -0
  49. htmlgraph/cost_analysis/analyzer.py +438 -0
  50. htmlgraph/dashboard.html +3356 -492
  51. htmlgraph-0.24.2.data/data/htmlgraph/dashboard.html → htmlgraph/dashboard.html.backup +2246 -248
  52. htmlgraph/dashboard.html.bak +7181 -0
  53. htmlgraph/dashboard.html.bak2 +7231 -0
  54. htmlgraph/dashboard.html.bak3 +7232 -0
  55. htmlgraph/db/__init__.py +38 -0
  56. htmlgraph/db/queries.py +790 -0
  57. htmlgraph/db/schema.py +1584 -0
  58. htmlgraph/deploy.py +26 -27
  59. htmlgraph/docs/API_REFERENCE.md +841 -0
  60. htmlgraph/docs/HTTP_API.md +750 -0
  61. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  62. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +710 -0
  63. htmlgraph/docs/README.md +533 -0
  64. htmlgraph/docs/version_check.py +3 -1
  65. htmlgraph/error_handler.py +544 -0
  66. htmlgraph/event_log.py +2 -0
  67. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  68. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  69. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  70. htmlgraph/hooks/__init__.py +8 -0
  71. htmlgraph/hooks/bootstrap.py +169 -0
  72. htmlgraph/hooks/cigs_pretool_enforcer.py +2 -2
  73. htmlgraph/hooks/concurrent_sessions.py +208 -0
  74. htmlgraph/hooks/context.py +318 -0
  75. htmlgraph/hooks/drift_handler.py +525 -0
  76. htmlgraph/hooks/event_tracker.py +496 -79
  77. htmlgraph/hooks/orchestrator.py +6 -4
  78. htmlgraph/hooks/orchestrator_reflector.py +4 -4
  79. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  80. htmlgraph/hooks/pretooluse.py +473 -6
  81. htmlgraph/hooks/prompt_analyzer.py +637 -0
  82. htmlgraph/hooks/session_handler.py +637 -0
  83. htmlgraph/hooks/state_manager.py +504 -0
  84. htmlgraph/hooks/subagent_stop.py +309 -0
  85. htmlgraph/hooks/task_enforcer.py +39 -0
  86. htmlgraph/hooks/validator.py +15 -11
  87. htmlgraph/models.py +111 -15
  88. htmlgraph/operations/fastapi_server.py +230 -0
  89. htmlgraph/orchestration/headless_spawner.py +344 -29
  90. htmlgraph/orchestration/live_events.py +377 -0
  91. htmlgraph/pydantic_models.py +476 -0
  92. htmlgraph/quality_gates.py +350 -0
  93. htmlgraph/repo_hash.py +511 -0
  94. htmlgraph/sdk.py +348 -10
  95. htmlgraph/server.py +194 -0
  96. htmlgraph/session_hooks.py +300 -0
  97. htmlgraph/session_manager.py +131 -1
  98. htmlgraph/session_registry.py +587 -0
  99. htmlgraph/session_state.py +436 -0
  100. htmlgraph/system_prompts.py +449 -0
  101. htmlgraph/templates/orchestration-view.html +350 -0
  102. htmlgraph/track_builder.py +19 -0
  103. htmlgraph/validation.py +115 -0
  104. htmlgraph-0.26.1.data/data/htmlgraph/dashboard.html +7458 -0
  105. {htmlgraph-0.24.2.dist-info → htmlgraph-0.26.1.dist-info}/METADATA +91 -64
  106. {htmlgraph-0.24.2.dist-info → htmlgraph-0.26.1.dist-info}/RECORD +112 -46
  107. {htmlgraph-0.24.2.data → htmlgraph-0.26.1.data}/data/htmlgraph/styles.css +0 -0
  108. {htmlgraph-0.24.2.data → htmlgraph-0.26.1.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  109. {htmlgraph-0.24.2.data → htmlgraph-0.26.1.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  110. {htmlgraph-0.24.2.data → htmlgraph-0.26.1.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  111. {htmlgraph-0.24.2.dist-info → htmlgraph-0.26.1.dist-info}/WHEEL +0 -0
  112. {htmlgraph-0.24.2.dist-info → htmlgraph-0.26.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,318 @@
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. "unknown" as last resort
105
+ #
106
+ # NOTE: We intentionally do NOT use SessionManager.get_active_session()
107
+ # as a fallback because the "active session" is stored in a global file
108
+ # (.htmlgraph/session.json) that's shared across all Claude windows.
109
+ # Using it would cause cross-window event contamination where tool calls
110
+ # from Window B get linked to UserQuery events from Window A.
111
+ session_id = (
112
+ hook_input.get("session_id")
113
+ or hook_input.get("sessionId")
114
+ or os.environ.get("HTMLGRAPH_SESSION_ID")
115
+ or os.environ.get("CLAUDE_SESSION_ID")
116
+ )
117
+
118
+ # Fallback to "unknown" - better than cross-window contamination
119
+ if not session_id:
120
+ session_id = "unknown"
121
+ logger.warning(
122
+ "Could not resolve session_id from hook_input or environment. "
123
+ "Events will not be linked to parent UserQuery. "
124
+ "For multi-window support, set HTMLGRAPH_SESSION_ID env var."
125
+ )
126
+
127
+ # Detect agent ID (priority order)
128
+ # 1. Explicit agent_id in hook input
129
+ # 2. HTMLGRAPH_AGENT_ID environment variable
130
+ # 3. CLAUDE_AGENT_NICKNAME environment variable (Claude Code)
131
+ # 4. Default to 'unknown'
132
+ agent_id = (
133
+ hook_input.get("agent_id")
134
+ or os.environ.get("HTMLGRAPH_AGENT_ID")
135
+ or os.environ.get("CLAUDE_AGENT_NICKNAME", "unknown")
136
+ )
137
+
138
+ # Detect model name (priority order)
139
+ # 1. Explicit model_name in hook input
140
+ # 2. CLAUDE_MODEL environment variable
141
+ # 3. HTMLGRAPH_MODEL environment variable
142
+ # 4. Status line cache (from ~/.cache/claude-code/status-{session_id}.json)
143
+ # 5. None (not available)
144
+ model_name = (
145
+ hook_input.get("model_name")
146
+ or hook_input.get("model")
147
+ or os.environ.get("CLAUDE_MODEL")
148
+ or os.environ.get("HTMLGRAPH_MODEL")
149
+ )
150
+
151
+ # Fallback: Try status line cache if model not detected yet
152
+ if not model_name and session_id and session_id != "unknown":
153
+ from htmlgraph.hooks.event_tracker import get_model_from_status_cache
154
+
155
+ model_name = get_model_from_status_cache(session_id)
156
+
157
+ logger.info(
158
+ f"Initializing hook context: session={session_id}, "
159
+ f"agent={agent_id}, model={model_name}, project={project_dir}"
160
+ )
161
+
162
+ return cls(
163
+ project_dir=project_dir,
164
+ graph_dir=graph_dir,
165
+ session_id=session_id,
166
+ agent_id=agent_id,
167
+ hook_input=hook_input,
168
+ model_name=model_name,
169
+ )
170
+
171
+ @property
172
+ def session_manager(self) -> Any:
173
+ """
174
+ Lazy-load and cache SessionManager instance.
175
+
176
+ Importing SessionManager is expensive (thousands of file system operations
177
+ for graph initialization), so we defer until first access.
178
+
179
+ Returns:
180
+ SessionManager instance for session tracking and activity attribution
181
+
182
+ Raises:
183
+ ImportError: If SessionManager cannot be imported
184
+ Exception: If SessionManager initialization fails
185
+
186
+ Note:
187
+ SessionManager is cached after first access. Multiple accesses
188
+ return the same instance.
189
+ """
190
+ if self._session_manager is not None:
191
+ return self._session_manager
192
+
193
+ try:
194
+ from htmlgraph.session_manager import SessionManager
195
+
196
+ logger.debug(f"Loading SessionManager for {self.graph_dir}")
197
+ self._session_manager = SessionManager(graph_dir=self.graph_dir)
198
+ logger.info("SessionManager loaded successfully")
199
+ return self._session_manager
200
+ except ImportError as e:
201
+ logger.error(f"Failed to import SessionManager: {e}")
202
+ raise
203
+ except Exception as e:
204
+ logger.error(f"Failed to initialize SessionManager: {e}")
205
+ raise
206
+
207
+ @property
208
+ def database(self) -> Any:
209
+ """
210
+ Lazy-load and cache HtmlGraphDB instance.
211
+
212
+ Database access is needed for event recording, but we defer initialization
213
+ until first access to minimize startup overhead.
214
+
215
+ Returns:
216
+ HtmlGraphDB instance for recording events and features
217
+
218
+ Raises:
219
+ ImportError: If HtmlGraphDB cannot be imported
220
+ Exception: If database connection fails
221
+
222
+ Note:
223
+ Database connection is cached after first access. Multiple accesses
224
+ return the same instance.
225
+ """
226
+ if self._database is not None:
227
+ return self._database
228
+
229
+ try:
230
+ from htmlgraph.db.schema import HtmlGraphDB
231
+
232
+ db_path = self.graph_dir / "htmlgraph.db"
233
+ logger.debug(f"Loading HtmlGraphDB at {db_path}")
234
+ self._database = HtmlGraphDB(str(db_path))
235
+ logger.info("HtmlGraphDB loaded successfully")
236
+ return self._database
237
+ except ImportError as e:
238
+ logger.error(f"Failed to import HtmlGraphDB: {e}")
239
+ raise
240
+ except Exception as e:
241
+ logger.error(f"Failed to initialize HtmlGraphDB: {e}")
242
+ raise
243
+
244
+ def close(self) -> None:
245
+ """
246
+ Clean up and close all resources gracefully.
247
+
248
+ Closes database connections and session manager resources.
249
+ Safe to call multiple times (idempotent).
250
+
251
+ This should be called in a finally block to ensure cleanup:
252
+
253
+ Example:
254
+ ```python
255
+ context = HookContext.from_input(hook_input)
256
+ try:
257
+ # Use context
258
+ context.session_manager.track_activity(...)
259
+ finally:
260
+ context.close() # Always cleanup
261
+ ```
262
+ """
263
+ # Close database if loaded
264
+ if self._database is not None:
265
+ try:
266
+ logger.debug("Closing database connection")
267
+ self._database.close()
268
+ self._database = None
269
+ logger.info("Database closed successfully")
270
+ except Exception as e:
271
+ logger.warning(f"Error closing database: {e}")
272
+
273
+ # Close session manager if loaded
274
+ if self._session_manager is not None:
275
+ try:
276
+ logger.debug("Closing session manager")
277
+ # SessionManager doesn't currently have a close method,
278
+ # but we keep this for future resource management
279
+ self._session_manager = None
280
+ logger.info("Session manager cleaned up")
281
+ except Exception as e:
282
+ logger.warning(f"Error closing session manager: {e}")
283
+
284
+ def log(self, level: str, message: str) -> None:
285
+ """
286
+ Unified logging for hooks.
287
+
288
+ Provides consistent logging across all hook modules with context
289
+ information (session_id, agent_id, project_dir).
290
+
291
+ Args:
292
+ level: Log level as string ('debug', 'info', 'warning', 'error', 'critical')
293
+ message: Message to log
294
+
295
+ Example:
296
+ ```python
297
+ context.log('info', 'Processing user query')
298
+ context.log('error', f'Failed to track activity: {error}')
299
+ ```
300
+ """
301
+ log_func = getattr(logger, level.lower(), logger.info)
302
+
303
+ # Prefix message with context for better debugging
304
+ context_msg = f"[{self.session_id[:8]}][{self.agent_id}] {message}"
305
+ log_func(context_msg)
306
+
307
+ def __enter__(self) -> "HookContext":
308
+ """Context manager entry."""
309
+ return self
310
+
311
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
312
+ """Context manager exit with resource cleanup."""
313
+ self.close()
314
+
315
+
316
+ __all__ = [
317
+ "HookContext",
318
+ ]