htmlgraph 0.24.2__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.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 +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.2.dist-info → htmlgraph-0.25.0.dist-info}/METADATA +91 -64
  97. {htmlgraph-0.24.2.dist-info → htmlgraph-0.25.0.dist-info}/RECORD +103 -42
  98. {htmlgraph-0.24.2.data → htmlgraph-0.25.0.data}/data/htmlgraph/styles.css +0 -0
  99. {htmlgraph-0.24.2.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  100. {htmlgraph-0.24.2.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  101. {htmlgraph-0.24.2.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  102. {htmlgraph-0.24.2.dist-info → htmlgraph-0.25.0.dist-info}/WHEEL +0 -0
  103. {htmlgraph-0.24.2.dist-info → htmlgraph-0.25.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,257 @@
1
+ """
2
+ PostToolUse Enhancement - Duration Calculation and Tool Trace Updates
3
+
4
+ This module handles the PostToolUse hook event and updates tool traces with:
5
+ 1. Execution end time (when the tool completed)
6
+ 2. Duration in milliseconds (end_time - start_time)
7
+ 3. Tool output (result of the tool execution)
8
+ 4. Status (Ok or Error)
9
+ 5. Error message (if status is Error)
10
+
11
+ The module correlates with PreToolUse via tool_use_id environment variable
12
+ and gracefully handles missing pre-events (logs warning, continues).
13
+
14
+ Design:
15
+ - Query tool_traces for matching tool_use_id
16
+ - Get start_time from pre-event
17
+ - Calculate duration_ms (end_time - start_time)
18
+ - Update tool_traces with: end_time, duration_ms, tool_output, status, error_message
19
+ - Handle missing pre-event gracefully (log warning, continue)
20
+ - Non-blocking - errors don't prevent tool execution continuation
21
+ """
22
+
23
+ import json
24
+ import logging
25
+ import os
26
+ from datetime import datetime, timezone
27
+ from typing import Any
28
+
29
+ from htmlgraph.db.schema import HtmlGraphDB
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ def calculate_duration(start_time_iso: str, end_time_iso: str) -> int:
35
+ """
36
+ Calculate duration in milliseconds between two ISO8601 UTC timestamps.
37
+
38
+ Args:
39
+ start_time_iso: ISO8601 UTC timestamp from PreToolUse (e.g., "2025-01-07T12:34:56.789000+00:00")
40
+ end_time_iso: ISO8601 UTC timestamp (now, e.g., "2025-01-07T12:34:57.123000+00:00")
41
+
42
+ Returns:
43
+ duration_ms: Integer milliseconds between timestamps (accurate within 1ms)
44
+
45
+ Raises:
46
+ ValueError: If timestamps cannot be parsed
47
+ TypeError: If inputs are not strings
48
+ """
49
+ try:
50
+ # Parse ISO8601 timestamps (handles timezone-aware datetimes)
51
+ start_dt = datetime.fromisoformat(start_time_iso.replace("Z", "+00:00"))
52
+ end_dt = datetime.fromisoformat(end_time_iso.replace("Z", "+00:00"))
53
+
54
+ # Calculate difference and convert to milliseconds
55
+ delta = end_dt - start_dt
56
+ duration_ms = int(delta.total_seconds() * 1000)
57
+
58
+ return duration_ms
59
+ except (ValueError, AttributeError, TypeError) as e:
60
+ logger.warning(f"Error calculating duration: {e}")
61
+ raise
62
+
63
+
64
+ def update_tool_trace(
65
+ tool_use_id: str,
66
+ tool_output: dict[str, Any] | None,
67
+ status: str,
68
+ error_message: str | None = None,
69
+ ) -> bool:
70
+ """
71
+ Update tool_traces table with execution end event.
72
+
73
+ Updates an existing tool trace (created by PreToolUse) with:
74
+ - end_time: Current UTC timestamp
75
+ - duration_ms: Milliseconds between start and end
76
+ - tool_output: Result of tool execution (JSON)
77
+ - status: 'Ok' or 'Error'
78
+ - error_message: Error details if status='Error'
79
+
80
+ Args:
81
+ tool_use_id: Correlation ID from PreToolUse event (from environment)
82
+ tool_output: Tool execution result (dict, will be JSON serialized)
83
+ status: 'Ok' or 'Error'
84
+ error_message: Error details if status='Error'
85
+
86
+ Returns:
87
+ True if update successful, False otherwise
88
+
89
+ Workflow:
90
+ 1. Query tool_traces for matching tool_use_id
91
+ 2. Get start_time from pre-event
92
+ 3. Calculate duration_ms (end_time - start_time)
93
+ 4. Update tool_traces with: end_time, duration_ms, tool_output, status, error_message
94
+ 5. Handle missing pre-event gracefully (log warning, continue)
95
+ """
96
+ try:
97
+ # Connect to database
98
+ db = HtmlGraphDB()
99
+
100
+ if not db.connection:
101
+ db.connect()
102
+
103
+ cursor = db.connection.cursor() # type: ignore[union-attr]
104
+
105
+ # Query tool_traces for matching tool_use_id
106
+ cursor.execute(
107
+ """
108
+ SELECT tool_use_id, start_time FROM tool_traces
109
+ WHERE tool_use_id = ?
110
+ """,
111
+ (tool_use_id,),
112
+ )
113
+
114
+ row = cursor.fetchone()
115
+
116
+ if not row:
117
+ # Missing pre-event - log warning but continue (graceful degradation)
118
+ logger.warning(
119
+ f"Could not find start event for tool_use_id={tool_use_id}. "
120
+ f"PreToolUse event may not have completed. Skipping duration update."
121
+ )
122
+ db.disconnect()
123
+ return False
124
+
125
+ # Get start_time from pre-event
126
+ start_time_iso = row[1]
127
+
128
+ # Calculate end_time (now in UTC)
129
+ end_time_iso = datetime.now(timezone.utc).isoformat()
130
+
131
+ # Calculate duration_ms
132
+ try:
133
+ duration_ms = calculate_duration(start_time_iso, end_time_iso)
134
+ except (ValueError, TypeError) as e:
135
+ logger.warning(
136
+ f"Could not calculate duration for tool_use_id={tool_use_id}: {e}. "
137
+ f"Using None for duration."
138
+ )
139
+ duration_ms = None
140
+
141
+ # Validate status
142
+ valid_statuses = {"Ok", "Error", "completed", "failed", "timeout"}
143
+ if status not in valid_statuses:
144
+ logger.warning(
145
+ f"Invalid status '{status}' for tool_use_id={tool_use_id}. "
146
+ f"Using 'Ok' as default."
147
+ )
148
+ status = "Ok"
149
+
150
+ # JSON serialize tool_output
151
+ tool_output_json = None
152
+ if tool_output:
153
+ try:
154
+ tool_output_json = json.dumps(tool_output)
155
+ except (TypeError, ValueError) as e:
156
+ logger.warning(
157
+ f"Could not JSON serialize tool_output for "
158
+ f"tool_use_id={tool_use_id}: {e}"
159
+ )
160
+ tool_output_json = json.dumps(
161
+ {"error": str(e), "output": str(tool_output)}
162
+ )
163
+
164
+ # Update tool_traces with: end_time, duration_ms, tool_output, status, error_message
165
+ cursor.execute(
166
+ """
167
+ UPDATE tool_traces
168
+ SET end_time = ?, duration_ms = ?, tool_output = ?,
169
+ status = ?, error_message = ?
170
+ WHERE tool_use_id = ?
171
+ """,
172
+ (
173
+ end_time_iso,
174
+ duration_ms,
175
+ tool_output_json,
176
+ status,
177
+ error_message,
178
+ tool_use_id,
179
+ ),
180
+ )
181
+
182
+ if not db.connection:
183
+ db.connect()
184
+
185
+ db.connection.commit() # type: ignore[union-attr]
186
+
187
+ logger.debug(
188
+ f"Updated tool trace: tool_use_id={tool_use_id}, "
189
+ f"duration_ms={duration_ms}, status={status}"
190
+ )
191
+
192
+ db.disconnect()
193
+ return True
194
+
195
+ except Exception as e:
196
+ # Log error but don't block
197
+ logger.error(f"Error updating tool trace for tool_use_id={tool_use_id}: {e}")
198
+ return False
199
+
200
+
201
+ def get_tool_use_id_from_context() -> str | None:
202
+ """
203
+ Get tool_use_id from environment (set by PreToolUse hook).
204
+
205
+ Returns:
206
+ tool_use_id string or None if not set
207
+ """
208
+ return os.environ.get("HTMLGRAPH_TOOL_USE_ID")
209
+
210
+
211
+ def determine_status_from_response(
212
+ tool_response: dict[str, Any] | None,
213
+ ) -> tuple[str, str | None]:
214
+ """
215
+ Determine status (Ok/Error) and error message from tool response.
216
+
217
+ Analyzes tool response to determine if execution was successful.
218
+ Returns (status, error_message) tuple.
219
+
220
+ Args:
221
+ tool_response: Tool execution response (dict)
222
+
223
+ Returns:
224
+ (status, error_message) where:
225
+ - status: 'Ok' or 'Error'
226
+ - error_message: Error details if Error, else None
227
+ """
228
+ if not tool_response:
229
+ return ("Ok", None)
230
+
231
+ if not isinstance(tool_response, dict):
232
+ return ("Ok", None)
233
+
234
+ # Check for explicit error indicators
235
+ # Bash tool: non-empty stderr
236
+ stderr = tool_response.get("stderr", "")
237
+ if stderr and isinstance(stderr, str) and stderr.strip():
238
+ return ("Error", f"stderr: {stderr[:500]}")
239
+
240
+ # Explicit error field
241
+ error_field = tool_response.get("error")
242
+ if error_field and str(error_field).strip():
243
+ return ("Error", str(error_field)[:500])
244
+
245
+ # success=false flag
246
+ if tool_response.get("success") is False:
247
+ reason = tool_response.get("reason", "Unknown error")
248
+ return ("Error", str(reason)[:500])
249
+
250
+ # status field indicating failure
251
+ status_field = tool_response.get("status")
252
+ if status_field and status_field.lower() in {"error", "failed", "failed"}:
253
+ reason = tool_response.get("message", "Unknown error")
254
+ return ("Error", str(reason)[:500])
255
+
256
+ # Default to success
257
+ return ("Ok", None)