htmlgraph 0.24.1__py3-none-any.whl → 0.25.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) 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 +2115 -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 +783 -0
  13. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  14. htmlgraph/api/templates/partials/activity-feed.html +570 -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 +3315 -492
  51. htmlgraph-0.24.1.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 +1334 -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/__init__.py +8 -0
  68. htmlgraph/hooks/bootstrap.py +169 -0
  69. htmlgraph/hooks/context.py +271 -0
  70. htmlgraph/hooks/drift_handler.py +521 -0
  71. htmlgraph/hooks/event_tracker.py +405 -15
  72. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  73. htmlgraph/hooks/pretooluse.py +476 -6
  74. htmlgraph/hooks/prompt_analyzer.py +648 -0
  75. htmlgraph/hooks/session_handler.py +583 -0
  76. htmlgraph/hooks/state_manager.py +501 -0
  77. htmlgraph/hooks/subagent_stop.py +309 -0
  78. htmlgraph/hooks/task_enforcer.py +39 -0
  79. htmlgraph/models.py +111 -15
  80. htmlgraph/operations/fastapi_server.py +230 -0
  81. htmlgraph/orchestration/headless_spawner.py +22 -14
  82. htmlgraph/pydantic_models.py +476 -0
  83. htmlgraph/quality_gates.py +350 -0
  84. htmlgraph/repo_hash.py +511 -0
  85. htmlgraph/sdk.py +348 -10
  86. htmlgraph/server.py +194 -0
  87. htmlgraph/session_hooks.py +300 -0
  88. htmlgraph/session_manager.py +131 -1
  89. htmlgraph/session_registry.py +587 -0
  90. htmlgraph/session_state.py +436 -0
  91. htmlgraph/system_prompts.py +449 -0
  92. htmlgraph/templates/orchestration-view.html +350 -0
  93. htmlgraph/track_builder.py +19 -0
  94. htmlgraph/validation.py +115 -0
  95. htmlgraph-0.25.0.data/data/htmlgraph/dashboard.html +7417 -0
  96. {htmlgraph-0.24.1.dist-info → htmlgraph-0.25.0.dist-info}/METADATA +91 -64
  97. {htmlgraph-0.24.1.dist-info → htmlgraph-0.25.0.dist-info}/RECORD +103 -42
  98. {htmlgraph-0.24.1.data → htmlgraph-0.25.0.data}/data/htmlgraph/styles.css +0 -0
  99. {htmlgraph-0.24.1.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  100. {htmlgraph-0.24.1.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  101. {htmlgraph-0.24.1.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  102. {htmlgraph-0.24.1.dist-info → htmlgraph-0.25.0.dist-info}/WHEEL +0 -0
  103. {htmlgraph-0.24.1.dist-info → htmlgraph-0.25.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env python3
2
+ """Bootstrap utilities for hook scripts.
3
+
4
+ Centralizes environment setup and project directory resolution used by all hooks.
5
+ Handles both development (src/python) and installed (package) modes.
6
+ """
7
+
8
+ import logging
9
+ import os
10
+ import subprocess
11
+ import sys
12
+ from pathlib import Path
13
+
14
+
15
+ def resolve_project_dir(cwd: str | None = None) -> str:
16
+ """Resolve the project directory with sensible fallbacks.
17
+
18
+ Hierarchy:
19
+ 1. CLAUDE_PROJECT_DIR environment variable (set by Claude Code)
20
+ 2. Git repository root (via git rev-parse --show-toplevel)
21
+ 3. Current working directory (or provided cwd)
22
+
23
+ This supports running hooks in multiple contexts:
24
+ - Within a Claude Code session
25
+ - In git repositories
26
+ - In arbitrary directories
27
+
28
+ Args:
29
+ cwd: Starting directory for git search. Defaults to os.getcwd().
30
+
31
+ Returns:
32
+ Absolute path to the project directory.
33
+
34
+ Raises:
35
+ No exceptions - always returns a valid path.
36
+ """
37
+ # First priority: Claude's explicit project directory
38
+ env_dir = os.environ.get("CLAUDE_PROJECT_DIR")
39
+ if env_dir:
40
+ return env_dir
41
+
42
+ # Second priority: Git repository root
43
+ start_dir = cwd or os.getcwd()
44
+ try:
45
+ result = subprocess.run(
46
+ ["git", "rev-parse", "--show-toplevel"],
47
+ capture_output=True,
48
+ text=True,
49
+ cwd=start_dir,
50
+ timeout=5,
51
+ )
52
+ if result.returncode == 0:
53
+ return result.stdout.strip()
54
+ except Exception:
55
+ # Git not available or not a repo - continue to fallback
56
+ pass
57
+
58
+ # Final fallback: current working directory
59
+ return start_dir
60
+
61
+
62
+ def bootstrap_pythonpath(project_dir: str) -> None:
63
+ """Bootstrap Python path for htmlgraph imports.
64
+
65
+ Handles two common deployment modes:
66
+ 1. Development: Running inside htmlgraph repository (src/python exists)
67
+ 2. Installed: Running where htmlgraph is installed as a package (do nothing)
68
+
69
+ This allows hooks to work correctly whether htmlgraph is:
70
+ - Being developed locally (add src/python to path)
71
+ - Installed in a virtual environment (already in path)
72
+ - Installed globally (already in path)
73
+
74
+ Args:
75
+ project_dir: Project directory from resolve_project_dir().
76
+
77
+ Returns:
78
+ None (modifies sys.path in-place).
79
+
80
+ Side Effects:
81
+ - Modifies sys.path to ensure htmlgraph is importable
82
+ - Adds .venv/lib/pythonX.Y/site-packages if virtual environment exists
83
+ - Adds src/python if in htmlgraph repository
84
+ """
85
+ project_path = Path(project_dir)
86
+
87
+ # First, try to use local virtual environment if it exists
88
+ venv = project_path / ".venv"
89
+ if venv.exists():
90
+ pyver = f"python{sys.version_info.major}.{sys.version_info.minor}"
91
+ candidates = [
92
+ venv / "lib" / pyver / "site-packages", # macOS/Linux
93
+ venv / "Lib" / "site-packages", # Windows
94
+ ]
95
+ for candidate in candidates:
96
+ if candidate.exists():
97
+ sys.path.insert(0, str(candidate))
98
+ break
99
+
100
+ # Then, add src/python if this is the htmlgraph repository itself
101
+ repo_src = project_path / "src" / "python"
102
+ if repo_src.exists():
103
+ sys.path.insert(0, str(repo_src))
104
+
105
+
106
+ def get_graph_dir(cwd: str | None = None) -> Path:
107
+ """Get the .htmlgraph directory path, creating it if necessary.
108
+
109
+ The .htmlgraph directory is the root for all HtmlGraph tracking:
110
+ - .htmlgraph/sessions/ - Session HTML files
111
+ - .htmlgraph/features/ - Feature tracking
112
+ - .htmlgraph/events/ - Event JSON files
113
+ - .htmlgraph/htmlgraph.db - SQLite database
114
+
115
+ Args:
116
+ cwd: Starting directory for project resolution. Defaults to os.getcwd().
117
+
118
+ Returns:
119
+ Path to the .htmlgraph directory (guaranteed to exist).
120
+
121
+ Raises:
122
+ OSError: If directory creation fails (e.g., permission denied).
123
+ """
124
+ project_dir = resolve_project_dir(cwd)
125
+ graph_dir = Path(project_dir) / ".htmlgraph"
126
+ graph_dir.mkdir(parents=True, exist_ok=True)
127
+ return graph_dir
128
+
129
+
130
+ def init_logger(name: str) -> logging.Logger:
131
+ """Initialize a logger with standardized configuration.
132
+
133
+ Sets up a logger for hook scripts with:
134
+ - Consistent format across all hooks
135
+ - basicConfig applied only once (subsequent calls are ignored)
136
+ - Named logger returned (can be used for filtering)
137
+
138
+ Format: "[TIMESTAMP] [LEVEL] [logger_name] message"
139
+
140
+ Args:
141
+ name: Logger name (typically __name__ from calling module).
142
+
143
+ Returns:
144
+ logging.Logger instance configured and ready to use.
145
+
146
+ Example:
147
+ ```python
148
+ logger = init_logger(__name__)
149
+ logger.info("Hook started")
150
+ logger.error("Something went wrong")
151
+ ```
152
+ """
153
+ # Configure basicConfig only once (subsequent calls are no-ops)
154
+ logging.basicConfig(
155
+ level=logging.INFO,
156
+ format="[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s",
157
+ datefmt="%Y-%m-%d %H:%M:%S",
158
+ )
159
+
160
+ # Return named logger for this module
161
+ return logging.getLogger(name)
162
+
163
+
164
+ __all__ = [
165
+ "resolve_project_dir",
166
+ "bootstrap_pythonpath",
167
+ "get_graph_dir",
168
+ "init_logger",
169
+ ]
@@ -0,0 +1,271 @@
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
+ _session_manager: Cached SessionManager instance (lazy-loaded)
41
+ _database: Cached HtmlGraphDB instance (lazy-loaded)
42
+ """
43
+
44
+ project_dir: str
45
+ graph_dir: Path
46
+ session_id: str
47
+ agent_id: str
48
+ hook_input: dict
49
+ _session_manager: Any | None = field(default=None, repr=False)
50
+ _database: Any | None = field(default=None, repr=False)
51
+
52
+ @classmethod
53
+ def from_input(cls, hook_input: dict) -> "HookContext":
54
+ """
55
+ Create HookContext from raw hook input.
56
+
57
+ Performs automatic environment resolution:
58
+ - Extracts session_id from hook_input
59
+ - Detects agent_id from environment or hook_input
60
+ - Resolves project directory via bootstrap
61
+ - Initializes graph directory
62
+
63
+ Args:
64
+ hook_input: Raw hook input dict from Claude Code hook system
65
+
66
+ Returns:
67
+ Initialized HookContext instance
68
+
69
+ Raises:
70
+ ImportError: If bootstrap module cannot be imported
71
+ OSError: If graph directory cannot be created
72
+
73
+ Example:
74
+ ```python
75
+ hook_input = {
76
+ 'session_id': 'sess-abc123',
77
+ 'type': 'pretooluse',
78
+ 'tool_name': 'Edit',
79
+ ...
80
+ }
81
+ context = HookContext.from_input(hook_input)
82
+ logger.info(f"Session: {context.session_id}, Agent: {context.agent_id}")
83
+ ```
84
+ """
85
+ # Import bootstrap locally to avoid circular imports
86
+ from htmlgraph.hooks.bootstrap import (
87
+ get_graph_dir,
88
+ resolve_project_dir,
89
+ )
90
+
91
+ # Extract session ID from hook input
92
+ session_id = hook_input.get("session_id", "unknown")
93
+
94
+ # Detect agent ID (priority order)
95
+ # 1. Explicit agent_id in hook input
96
+ # 2. HTMLGRAPH_AGENT_ID environment variable
97
+ # 3. CLAUDE_AGENT_NICKNAME environment variable (Claude Code)
98
+ # 4. Default to 'unknown'
99
+ agent_id = (
100
+ hook_input.get("agent_id")
101
+ or os.environ.get("HTMLGRAPH_AGENT_ID")
102
+ or os.environ.get("CLAUDE_AGENT_NICKNAME", "unknown")
103
+ )
104
+
105
+ # Resolve project directory with fallbacks
106
+ project_dir = resolve_project_dir()
107
+
108
+ # Get or create graph directory
109
+ graph_dir = get_graph_dir(project_dir)
110
+
111
+ logger.info(
112
+ f"Initializing hook context: session={session_id}, "
113
+ f"agent={agent_id}, project={project_dir}"
114
+ )
115
+
116
+ return cls(
117
+ project_dir=project_dir,
118
+ graph_dir=graph_dir,
119
+ session_id=session_id,
120
+ agent_id=agent_id,
121
+ hook_input=hook_input,
122
+ )
123
+
124
+ @property
125
+ def session_manager(self) -> Any:
126
+ """
127
+ Lazy-load and cache SessionManager instance.
128
+
129
+ Importing SessionManager is expensive (thousands of file system operations
130
+ for graph initialization), so we defer until first access.
131
+
132
+ Returns:
133
+ SessionManager instance for session tracking and activity attribution
134
+
135
+ Raises:
136
+ ImportError: If SessionManager cannot be imported
137
+ Exception: If SessionManager initialization fails
138
+
139
+ Note:
140
+ SessionManager is cached after first access. Multiple accesses
141
+ return the same instance.
142
+ """
143
+ if self._session_manager is not None:
144
+ return self._session_manager
145
+
146
+ try:
147
+ from htmlgraph.session_manager import SessionManager
148
+
149
+ logger.debug(f"Loading SessionManager for {self.graph_dir}")
150
+ self._session_manager = SessionManager(graph_dir=self.graph_dir)
151
+ logger.info("SessionManager loaded successfully")
152
+ return self._session_manager
153
+ except ImportError as e:
154
+ logger.error(f"Failed to import SessionManager: {e}")
155
+ raise
156
+ except Exception as e:
157
+ logger.error(f"Failed to initialize SessionManager: {e}")
158
+ raise
159
+
160
+ @property
161
+ def database(self) -> Any:
162
+ """
163
+ Lazy-load and cache HtmlGraphDB instance.
164
+
165
+ Database access is needed for event recording, but we defer initialization
166
+ until first access to minimize startup overhead.
167
+
168
+ Returns:
169
+ HtmlGraphDB instance for recording events and features
170
+
171
+ Raises:
172
+ ImportError: If HtmlGraphDB cannot be imported
173
+ Exception: If database connection fails
174
+
175
+ Note:
176
+ Database connection is cached after first access. Multiple accesses
177
+ return the same instance.
178
+ """
179
+ if self._database is not None:
180
+ return self._database
181
+
182
+ try:
183
+ from htmlgraph.db.schema import HtmlGraphDB
184
+
185
+ db_path = self.graph_dir / "htmlgraph.db"
186
+ logger.debug(f"Loading HtmlGraphDB at {db_path}")
187
+ self._database = HtmlGraphDB(str(db_path))
188
+ logger.info("HtmlGraphDB loaded successfully")
189
+ return self._database
190
+ except ImportError as e:
191
+ logger.error(f"Failed to import HtmlGraphDB: {e}")
192
+ raise
193
+ except Exception as e:
194
+ logger.error(f"Failed to initialize HtmlGraphDB: {e}")
195
+ raise
196
+
197
+ def close(self) -> None:
198
+ """
199
+ Clean up and close all resources gracefully.
200
+
201
+ Closes database connections and session manager resources.
202
+ Safe to call multiple times (idempotent).
203
+
204
+ This should be called in a finally block to ensure cleanup:
205
+
206
+ Example:
207
+ ```python
208
+ context = HookContext.from_input(hook_input)
209
+ try:
210
+ # Use context
211
+ context.session_manager.track_activity(...)
212
+ finally:
213
+ context.close() # Always cleanup
214
+ ```
215
+ """
216
+ # Close database if loaded
217
+ if self._database is not None:
218
+ try:
219
+ logger.debug("Closing database connection")
220
+ self._database.close()
221
+ self._database = None
222
+ logger.info("Database closed successfully")
223
+ except Exception as e:
224
+ logger.warning(f"Error closing database: {e}")
225
+
226
+ # Close session manager if loaded
227
+ if self._session_manager is not None:
228
+ try:
229
+ logger.debug("Closing session manager")
230
+ # SessionManager doesn't currently have a close method,
231
+ # but we keep this for future resource management
232
+ self._session_manager = None
233
+ logger.info("Session manager cleaned up")
234
+ except Exception as e:
235
+ logger.warning(f"Error closing session manager: {e}")
236
+
237
+ def log(self, level: str, message: str) -> None:
238
+ """
239
+ Unified logging for hooks.
240
+
241
+ Provides consistent logging across all hook modules with context
242
+ information (session_id, agent_id, project_dir).
243
+
244
+ Args:
245
+ level: Log level as string ('debug', 'info', 'warning', 'error', 'critical')
246
+ message: Message to log
247
+
248
+ Example:
249
+ ```python
250
+ context.log('info', 'Processing user query')
251
+ context.log('error', f'Failed to track activity: {error}')
252
+ ```
253
+ """
254
+ log_func = getattr(logger, level.lower(), logger.info)
255
+
256
+ # Prefix message with context for better debugging
257
+ context_msg = f"[{self.session_id[:8]}][{self.agent_id}] {message}"
258
+ log_func(context_msg)
259
+
260
+ def __enter__(self) -> "HookContext":
261
+ """Context manager entry."""
262
+ return self
263
+
264
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
265
+ """Context manager exit with resource cleanup."""
266
+ self.close()
267
+
268
+
269
+ __all__ = [
270
+ "HookContext",
271
+ ]