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.
Files changed (331) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
  2. htmlgraph/.htmlgraph/agents.json +72 -0
  3. htmlgraph/.htmlgraph/htmlgraph.db +0 -0
  4. htmlgraph/__init__.py +173 -17
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +127 -0
  7. htmlgraph/agent_registry.py +45 -30
  8. htmlgraph/agents.py +160 -107
  9. htmlgraph/analytics/__init__.py +9 -2
  10. htmlgraph/analytics/cli.py +190 -51
  11. htmlgraph/analytics/cost_analyzer.py +391 -0
  12. htmlgraph/analytics/cost_monitor.py +664 -0
  13. htmlgraph/analytics/cost_reporter.py +675 -0
  14. htmlgraph/analytics/cross_session.py +617 -0
  15. htmlgraph/analytics/dependency.py +192 -100
  16. htmlgraph/analytics/pattern_learning.py +771 -0
  17. htmlgraph/analytics/session_graph.py +707 -0
  18. htmlgraph/analytics/strategic/__init__.py +80 -0
  19. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  20. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  21. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  22. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  23. htmlgraph/analytics/work_type.py +190 -14
  24. htmlgraph/analytics_index.py +135 -51
  25. htmlgraph/api/__init__.py +3 -0
  26. htmlgraph/api/cost_alerts_websocket.py +416 -0
  27. htmlgraph/api/main.py +2498 -0
  28. htmlgraph/api/static/htmx.min.js +1 -0
  29. htmlgraph/api/static/style-redesign.css +1344 -0
  30. htmlgraph/api/static/style.css +1079 -0
  31. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  32. htmlgraph/api/templates/dashboard.html +794 -0
  33. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  34. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  35. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  36. htmlgraph/api/templates/partials/agents.html +317 -0
  37. htmlgraph/api/templates/partials/event-traces.html +373 -0
  38. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  39. htmlgraph/api/templates/partials/features.html +578 -0
  40. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  41. htmlgraph/api/templates/partials/metrics.html +346 -0
  42. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  43. htmlgraph/api/templates/partials/orchestration.html +198 -0
  44. htmlgraph/api/templates/partials/spawners.html +375 -0
  45. htmlgraph/api/templates/partials/work-items.html +613 -0
  46. htmlgraph/api/websocket.py +538 -0
  47. htmlgraph/archive/__init__.py +24 -0
  48. htmlgraph/archive/bloom.py +234 -0
  49. htmlgraph/archive/fts.py +297 -0
  50. htmlgraph/archive/manager.py +583 -0
  51. htmlgraph/archive/search.py +244 -0
  52. htmlgraph/atomic_ops.py +560 -0
  53. htmlgraph/attribute_index.py +208 -0
  54. htmlgraph/bounded_paths.py +539 -0
  55. htmlgraph/builders/__init__.py +14 -0
  56. htmlgraph/builders/base.py +118 -29
  57. htmlgraph/builders/bug.py +150 -0
  58. htmlgraph/builders/chore.py +119 -0
  59. htmlgraph/builders/epic.py +150 -0
  60. htmlgraph/builders/feature.py +31 -6
  61. htmlgraph/builders/insight.py +195 -0
  62. htmlgraph/builders/metric.py +217 -0
  63. htmlgraph/builders/pattern.py +202 -0
  64. htmlgraph/builders/phase.py +162 -0
  65. htmlgraph/builders/spike.py +52 -19
  66. htmlgraph/builders/track.py +148 -72
  67. htmlgraph/cigs/__init__.py +81 -0
  68. htmlgraph/cigs/autonomy.py +385 -0
  69. htmlgraph/cigs/cost.py +475 -0
  70. htmlgraph/cigs/messages_basic.py +472 -0
  71. htmlgraph/cigs/messaging.py +365 -0
  72. htmlgraph/cigs/models.py +771 -0
  73. htmlgraph/cigs/pattern_storage.py +427 -0
  74. htmlgraph/cigs/patterns.py +503 -0
  75. htmlgraph/cigs/posttool_analyzer.py +234 -0
  76. htmlgraph/cigs/reporter.py +818 -0
  77. htmlgraph/cigs/tracker.py +317 -0
  78. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  79. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  80. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  81. htmlgraph/cli/__init__.py +42 -0
  82. htmlgraph/cli/__main__.py +6 -0
  83. htmlgraph/cli/analytics.py +1424 -0
  84. htmlgraph/cli/base.py +685 -0
  85. htmlgraph/cli/constants.py +206 -0
  86. htmlgraph/cli/core.py +954 -0
  87. htmlgraph/cli/main.py +147 -0
  88. htmlgraph/cli/models.py +475 -0
  89. htmlgraph/cli/templates/__init__.py +1 -0
  90. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  91. htmlgraph/cli/work/__init__.py +239 -0
  92. htmlgraph/cli/work/browse.py +115 -0
  93. htmlgraph/cli/work/features.py +568 -0
  94. htmlgraph/cli/work/orchestration.py +676 -0
  95. htmlgraph/cli/work/report.py +728 -0
  96. htmlgraph/cli/work/sessions.py +466 -0
  97. htmlgraph/cli/work/snapshot.py +559 -0
  98. htmlgraph/cli/work/tracks.py +486 -0
  99. htmlgraph/cli_commands/__init__.py +1 -0
  100. htmlgraph/cli_commands/feature.py +195 -0
  101. htmlgraph/cli_framework.py +115 -0
  102. htmlgraph/collections/__init__.py +18 -0
  103. htmlgraph/collections/base.py +415 -98
  104. htmlgraph/collections/bug.py +53 -0
  105. htmlgraph/collections/chore.py +53 -0
  106. htmlgraph/collections/epic.py +53 -0
  107. htmlgraph/collections/feature.py +12 -26
  108. htmlgraph/collections/insight.py +100 -0
  109. htmlgraph/collections/metric.py +92 -0
  110. htmlgraph/collections/pattern.py +97 -0
  111. htmlgraph/collections/phase.py +53 -0
  112. htmlgraph/collections/session.py +194 -0
  113. htmlgraph/collections/spike.py +56 -16
  114. htmlgraph/collections/task_delegation.py +241 -0
  115. htmlgraph/collections/todo.py +511 -0
  116. htmlgraph/collections/traces.py +487 -0
  117. htmlgraph/config/cost_models.json +56 -0
  118. htmlgraph/config.py +190 -0
  119. htmlgraph/context_analytics.py +344 -0
  120. htmlgraph/converter.py +216 -28
  121. htmlgraph/cost_analysis/__init__.py +5 -0
  122. htmlgraph/cost_analysis/analyzer.py +438 -0
  123. htmlgraph/dashboard.html +2406 -307
  124. htmlgraph/dashboard.html.backup +6592 -0
  125. htmlgraph/dashboard.html.bak +7181 -0
  126. htmlgraph/dashboard.html.bak2 +7231 -0
  127. htmlgraph/dashboard.html.bak3 +7232 -0
  128. htmlgraph/db/__init__.py +38 -0
  129. htmlgraph/db/queries.py +790 -0
  130. htmlgraph/db/schema.py +1788 -0
  131. htmlgraph/decorators.py +317 -0
  132. htmlgraph/dependency_models.py +19 -2
  133. htmlgraph/deploy.py +142 -125
  134. htmlgraph/deployment_models.py +474 -0
  135. htmlgraph/docs/API_REFERENCE.md +841 -0
  136. htmlgraph/docs/HTTP_API.md +750 -0
  137. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  138. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  139. htmlgraph/docs/README.md +532 -0
  140. htmlgraph/docs/__init__.py +77 -0
  141. htmlgraph/docs/docs_version.py +55 -0
  142. htmlgraph/docs/metadata.py +93 -0
  143. htmlgraph/docs/migrations.py +232 -0
  144. htmlgraph/docs/template_engine.py +143 -0
  145. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  146. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  147. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  148. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  149. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  150. htmlgraph/docs/version_check.py +163 -0
  151. htmlgraph/edge_index.py +182 -27
  152. htmlgraph/error_handler.py +544 -0
  153. htmlgraph/event_log.py +100 -52
  154. htmlgraph/event_migration.py +13 -4
  155. htmlgraph/exceptions.py +49 -0
  156. htmlgraph/file_watcher.py +101 -28
  157. htmlgraph/find_api.py +75 -63
  158. htmlgraph/git_events.py +145 -63
  159. htmlgraph/graph.py +1122 -106
  160. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  161. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  162. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  163. htmlgraph/hooks/__init__.py +45 -0
  164. htmlgraph/hooks/bootstrap.py +169 -0
  165. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  166. htmlgraph/hooks/concurrent_sessions.py +208 -0
  167. htmlgraph/hooks/context.py +350 -0
  168. htmlgraph/hooks/drift_handler.py +525 -0
  169. htmlgraph/hooks/event_tracker.py +1314 -0
  170. htmlgraph/hooks/git_commands.py +175 -0
  171. htmlgraph/hooks/hooks-config.example.json +12 -0
  172. htmlgraph/hooks/installer.py +343 -0
  173. htmlgraph/hooks/orchestrator.py +674 -0
  174. htmlgraph/hooks/orchestrator_reflector.py +223 -0
  175. htmlgraph/hooks/post-checkout.sh +28 -0
  176. htmlgraph/hooks/post-commit.sh +24 -0
  177. htmlgraph/hooks/post-merge.sh +26 -0
  178. htmlgraph/hooks/post_tool_use_failure.py +273 -0
  179. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  180. htmlgraph/hooks/posttooluse.py +408 -0
  181. htmlgraph/hooks/pre-commit.sh +94 -0
  182. htmlgraph/hooks/pre-push.sh +28 -0
  183. htmlgraph/hooks/pretooluse.py +819 -0
  184. htmlgraph/hooks/prompt_analyzer.py +637 -0
  185. htmlgraph/hooks/session_handler.py +668 -0
  186. htmlgraph/hooks/session_summary.py +395 -0
  187. htmlgraph/hooks/state_manager.py +504 -0
  188. htmlgraph/hooks/subagent_detection.py +202 -0
  189. htmlgraph/hooks/subagent_stop.py +369 -0
  190. htmlgraph/hooks/task_enforcer.py +255 -0
  191. htmlgraph/hooks/task_validator.py +177 -0
  192. htmlgraph/hooks/validator.py +628 -0
  193. htmlgraph/ids.py +41 -27
  194. htmlgraph/index.d.ts +286 -0
  195. htmlgraph/learning.py +767 -0
  196. htmlgraph/mcp_server.py +69 -23
  197. htmlgraph/models.py +1586 -87
  198. htmlgraph/operations/README.md +62 -0
  199. htmlgraph/operations/__init__.py +79 -0
  200. htmlgraph/operations/analytics.py +339 -0
  201. htmlgraph/operations/bootstrap.py +289 -0
  202. htmlgraph/operations/events.py +244 -0
  203. htmlgraph/operations/fastapi_server.py +231 -0
  204. htmlgraph/operations/hooks.py +350 -0
  205. htmlgraph/operations/initialization.py +597 -0
  206. htmlgraph/operations/initialization.py.backup +228 -0
  207. htmlgraph/operations/server.py +303 -0
  208. htmlgraph/orchestration/__init__.py +58 -0
  209. htmlgraph/orchestration/claude_launcher.py +179 -0
  210. htmlgraph/orchestration/command_builder.py +72 -0
  211. htmlgraph/orchestration/headless_spawner.py +281 -0
  212. htmlgraph/orchestration/live_events.py +377 -0
  213. htmlgraph/orchestration/model_selection.py +327 -0
  214. htmlgraph/orchestration/plugin_manager.py +140 -0
  215. htmlgraph/orchestration/prompts.py +137 -0
  216. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  217. htmlgraph/orchestration/spawners/__init__.py +16 -0
  218. htmlgraph/orchestration/spawners/base.py +194 -0
  219. htmlgraph/orchestration/spawners/claude.py +173 -0
  220. htmlgraph/orchestration/spawners/codex.py +435 -0
  221. htmlgraph/orchestration/spawners/copilot.py +294 -0
  222. htmlgraph/orchestration/spawners/gemini.py +471 -0
  223. htmlgraph/orchestration/subprocess_runner.py +36 -0
  224. htmlgraph/orchestration/task_coordination.py +343 -0
  225. htmlgraph/orchestration.md +563 -0
  226. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  227. htmlgraph/orchestrator.py +669 -0
  228. htmlgraph/orchestrator_config.py +357 -0
  229. htmlgraph/orchestrator_mode.py +328 -0
  230. htmlgraph/orchestrator_validator.py +133 -0
  231. htmlgraph/parallel.py +646 -0
  232. htmlgraph/parser.py +160 -35
  233. htmlgraph/path_query.py +608 -0
  234. htmlgraph/pattern_matcher.py +636 -0
  235. htmlgraph/planning.py +147 -52
  236. htmlgraph/pydantic_models.py +476 -0
  237. htmlgraph/quality_gates.py +350 -0
  238. htmlgraph/query_builder.py +109 -72
  239. htmlgraph/query_composer.py +509 -0
  240. htmlgraph/reflection.py +443 -0
  241. htmlgraph/refs.py +344 -0
  242. htmlgraph/repo_hash.py +512 -0
  243. htmlgraph/repositories/__init__.py +292 -0
  244. htmlgraph/repositories/analytics_repository.py +455 -0
  245. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  246. htmlgraph/repositories/feature_repository.py +581 -0
  247. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  248. htmlgraph/repositories/feature_repository_memory.py +607 -0
  249. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  250. htmlgraph/repositories/filter_service.py +620 -0
  251. htmlgraph/repositories/filter_service_standard.py +445 -0
  252. htmlgraph/repositories/shared_cache.py +621 -0
  253. htmlgraph/repositories/shared_cache_memory.py +395 -0
  254. htmlgraph/repositories/track_repository.py +552 -0
  255. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  256. htmlgraph/repositories/track_repository_memory.py +508 -0
  257. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  258. htmlgraph/routing.py +8 -19
  259. htmlgraph/scripts/deploy.py +1 -2
  260. htmlgraph/sdk/__init__.py +398 -0
  261. htmlgraph/sdk/__init__.pyi +14 -0
  262. htmlgraph/sdk/analytics/__init__.py +19 -0
  263. htmlgraph/sdk/analytics/engine.py +155 -0
  264. htmlgraph/sdk/analytics/helpers.py +178 -0
  265. htmlgraph/sdk/analytics/registry.py +109 -0
  266. htmlgraph/sdk/base.py +484 -0
  267. htmlgraph/sdk/constants.py +216 -0
  268. htmlgraph/sdk/core.pyi +308 -0
  269. htmlgraph/sdk/discovery.py +120 -0
  270. htmlgraph/sdk/help/__init__.py +12 -0
  271. htmlgraph/sdk/help/mixin.py +699 -0
  272. htmlgraph/sdk/mixins/__init__.py +15 -0
  273. htmlgraph/sdk/mixins/attribution.py +113 -0
  274. htmlgraph/sdk/mixins/mixin.py +410 -0
  275. htmlgraph/sdk/operations/__init__.py +12 -0
  276. htmlgraph/sdk/operations/mixin.py +427 -0
  277. htmlgraph/sdk/orchestration/__init__.py +17 -0
  278. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  279. htmlgraph/sdk/orchestration/spawner.py +204 -0
  280. htmlgraph/sdk/planning/__init__.py +19 -0
  281. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  282. htmlgraph/sdk/planning/mixin.py +211 -0
  283. htmlgraph/sdk/planning/parallel.py +186 -0
  284. htmlgraph/sdk/planning/queue.py +210 -0
  285. htmlgraph/sdk/planning/recommendations.py +87 -0
  286. htmlgraph/sdk/planning/smart_planning.py +319 -0
  287. htmlgraph/sdk/session/__init__.py +19 -0
  288. htmlgraph/sdk/session/continuity.py +57 -0
  289. htmlgraph/sdk/session/handoff.py +110 -0
  290. htmlgraph/sdk/session/info.py +309 -0
  291. htmlgraph/sdk/session/manager.py +103 -0
  292. htmlgraph/sdk/strategic/__init__.py +26 -0
  293. htmlgraph/sdk/strategic/mixin.py +563 -0
  294. htmlgraph/server.py +685 -180
  295. htmlgraph/services/__init__.py +10 -0
  296. htmlgraph/services/claiming.py +199 -0
  297. htmlgraph/session_hooks.py +300 -0
  298. htmlgraph/session_manager.py +1392 -175
  299. htmlgraph/session_registry.py +587 -0
  300. htmlgraph/session_state.py +436 -0
  301. htmlgraph/session_warning.py +201 -0
  302. htmlgraph/sessions/__init__.py +23 -0
  303. htmlgraph/sessions/handoff.py +756 -0
  304. htmlgraph/setup.py +34 -17
  305. htmlgraph/spike_index.py +143 -0
  306. htmlgraph/sync_docs.py +12 -15
  307. htmlgraph/system_prompts.py +450 -0
  308. htmlgraph/templates/AGENTS.md.template +366 -0
  309. htmlgraph/templates/CLAUDE.md.template +97 -0
  310. htmlgraph/templates/GEMINI.md.template +87 -0
  311. htmlgraph/templates/orchestration-view.html +350 -0
  312. htmlgraph/track_builder.py +146 -15
  313. htmlgraph/track_manager.py +69 -21
  314. htmlgraph/transcript.py +890 -0
  315. htmlgraph/transcript_analytics.py +699 -0
  316. htmlgraph/types.py +323 -0
  317. htmlgraph/validation.py +115 -0
  318. htmlgraph/watch.py +8 -5
  319. htmlgraph/work_type_utils.py +3 -2
  320. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
  321. htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
  322. htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
  323. htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
  324. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
  325. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  326. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  327. htmlgraph/cli.py +0 -2688
  328. htmlgraph/sdk.py +0 -709
  329. htmlgraph-0.9.3.dist-info/RECORD +0 -61
  330. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  331. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,223 @@
1
+ import logging
2
+
3
+ logger = logging.getLogger(__name__)
4
+
5
+ """
6
+ Orchestrator Reflection Module
7
+
8
+ Detects when Claude executes Python code directly via Bash and provides
9
+ a gentle reflection prompt to encourage delegation to subagents.
10
+
11
+ This helps reinforce orchestrator patterns:
12
+ - Delegation over direct execution
13
+ - Parallel Task() calls for efficiency
14
+ - Work item tracking for all efforts
15
+
16
+ Usage:
17
+ from htmlgraph.hooks.orchestrator_reflector import orchestrator_reflect
18
+
19
+ # In a posttooluse hook
20
+ hook_input = {
21
+ "tool_name": "Bash",
22
+ "tool_input": {"command": "uv run pytest"}
23
+ }
24
+ result = orchestrator_reflect(hook_input)
25
+ # Returns: {"continue": True, "hookSpecificOutput": {...}}
26
+ """
27
+
28
+ import re
29
+ from typing import Any, TypedDict
30
+
31
+
32
+ class HookSpecificOutput(TypedDict):
33
+ """Hook-specific output structure."""
34
+
35
+ hookEventName: str
36
+ additionalContext: str
37
+
38
+
39
+ class HookResponse(TypedDict):
40
+ """Hook response structure."""
41
+
42
+ continue_: bool
43
+ hookSpecificOutput: HookSpecificOutput | None
44
+
45
+
46
+ # Python execution patterns to detect
47
+ PYTHON_EXECUTION_PATTERNS = [
48
+ r"\buv\s+run\b", # uv run <anything>
49
+ r"\bpython\s+-c\b", # python -c "code"
50
+ r"\bpython\s+[\w/.-]+\.py\b", # python script.py
51
+ r"\bpython\s+-m\s+\w+", # python -m module
52
+ r"\bpytest\b", # pytest
53
+ r"\bpython3\s+", # python3 command
54
+ ]
55
+
56
+ # Commands to exclude from detection
57
+ EXCLUDED_COMMAND_PREFIXES = ("git ", " git ", "ls ", "cat ", "grep ", "find ")
58
+
59
+
60
+ def is_python_execution(command: str) -> bool:
61
+ """
62
+ Detect if a bash command is executing Python code.
63
+
64
+ Patterns to detect:
65
+ - uv run <script>
66
+ - python -c <code>
67
+ - python <script>
68
+ - pytest
69
+ - python -m <module>
70
+
71
+ Excludes:
72
+ - git commands (even if they mention python)
73
+ - simple tool calls that happen to have "python" in path
74
+
75
+ Args:
76
+ command: The bash command to analyze
77
+
78
+ Returns:
79
+ True if the command executes Python code
80
+ """
81
+ # Normalize command
82
+ cmd = command.strip().lower()
83
+
84
+ # Exclude git commands entirely
85
+ if cmd.startswith("git ") or " git " in cmd:
86
+ return False
87
+
88
+ # Exclude simple file operations
89
+ if cmd.startswith(EXCLUDED_COMMAND_PREFIXES):
90
+ return False
91
+
92
+ # Check for Python execution patterns
93
+ for pattern in PYTHON_EXECUTION_PATTERNS:
94
+ if re.search(pattern, cmd):
95
+ return True
96
+
97
+ return False
98
+
99
+
100
+ def should_reflect(hook_input: dict[str, Any]) -> tuple[bool, str]:
101
+ """
102
+ Check if we should show reflection prompt.
103
+
104
+ Args:
105
+ hook_input: The hook input data containing tool name and tool input
106
+
107
+ Returns:
108
+ (should_show, command_preview) tuple where:
109
+ - should_show: True if reflection should be shown
110
+ - command_preview: Preview of the command (first 60 chars)
111
+ """
112
+ tool_name = hook_input.get("tool_name", "")
113
+
114
+ # Only check Bash tool usage
115
+ if tool_name != "Bash":
116
+ return False, ""
117
+
118
+ # Get the command
119
+ tool_input = hook_input.get("tool_input", {})
120
+ command = tool_input.get("command", "")
121
+
122
+ if not command:
123
+ return False, ""
124
+
125
+ # Check if it's Python execution
126
+ if is_python_execution(command):
127
+ # Create a preview of the command (first 60 chars)
128
+ preview = command[:60].replace("\n", " ")
129
+ if len(command) > 60:
130
+ preview += "..."
131
+ return True, preview
132
+
133
+ return False, ""
134
+
135
+
136
+ def build_reflection_message(command_preview: str) -> str:
137
+ """
138
+ Build the reflection message for orchestrator.
139
+
140
+ This should be:
141
+ - Gentle and non-blocking
142
+ - Encourage reflection without being preachy
143
+ - Point to specific alternatives
144
+
145
+ Args:
146
+ command_preview: Preview of the executed command
147
+
148
+ Returns:
149
+ The formatted reflection message
150
+ """
151
+ return f"""ORCHESTRATOR REFLECTION: You executed code directly.
152
+
153
+ Command: {command_preview}
154
+
155
+ Ask yourself:
156
+ - Could this have been delegated to a subagent?
157
+ - Would parallel Task() calls have been faster?
158
+ - Is a work item tracking this effort?
159
+
160
+ Continue, but consider delegation for similar future tasks."""
161
+
162
+
163
+ def orchestrator_reflect(tool_input: dict[str, Any]) -> dict[str, Any]:
164
+ """
165
+ Main API function for orchestrator reflection.
166
+
167
+ Analyzes tool usage and provides reflection feedback when direct
168
+ Python execution is detected.
169
+
170
+ Args:
171
+ tool_input: Hook input containing tool_name and tool_input fields
172
+
173
+ Returns:
174
+ Hook response dict with continue=True and optional hookSpecificOutput
175
+
176
+ Example:
177
+ >>> hook_input = {
178
+ ... "tool_name": "Bash",
179
+ ... "tool_input": {"command": "uv run pytest"}
180
+ ... }
181
+ >>> result = orchestrator_reflect(hook_input)
182
+ >>> result["continue"]
183
+ True
184
+ >>> "hookSpecificOutput" in result
185
+ True
186
+ """
187
+ # Check if we should reflect
188
+ should_show, command_preview = should_reflect(tool_input)
189
+
190
+ # Build response
191
+ response: dict[str, Any] = {"continue": True}
192
+
193
+ if should_show:
194
+ reflection = build_reflection_message(command_preview)
195
+ response["hookSpecificOutput"] = {
196
+ "hookEventName": "PostToolUse",
197
+ "additionalContext": reflection,
198
+ }
199
+
200
+ return response
201
+
202
+
203
+ def main() -> None:
204
+ """Hook entry point for script wrapper."""
205
+ import json
206
+ import os
207
+ import sys
208
+
209
+ # Check if tracking is disabled
210
+ if os.environ.get("HTMLGRAPH_DISABLE_TRACKING") == "1":
211
+ print(json.dumps({"continue": True}))
212
+ sys.exit(0)
213
+
214
+ try:
215
+ hook_input = json.load(sys.stdin)
216
+ except json.JSONDecodeError:
217
+ hook_input = {}
218
+
219
+ # Run reflection logic
220
+ response = orchestrator_reflect(hook_input)
221
+
222
+ # Output JSON response
223
+ print(json.dumps(response))
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+ #
3
+ # HtmlGraph Post-Checkout Hook
4
+ # Logs branch switches / checkouts for continuity tracking
5
+ #
6
+
7
+ set +e
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
10
+ cd "$PROJECT_ROOT" || exit 0
11
+
12
+ if [ ! -d ".htmlgraph" ]; then
13
+ exit 0
14
+ fi
15
+
16
+ OLD_HEAD="$1"
17
+ NEW_HEAD="$2"
18
+ FLAG="$3"
19
+
20
+ if ! command -v htmlgraph &> /dev/null; then
21
+ if command -v python3 &> /dev/null; then
22
+ python3 -m htmlgraph.git_events checkout "$OLD_HEAD" "$NEW_HEAD" "$FLAG" &> /dev/null &
23
+ fi
24
+ exit 0
25
+ fi
26
+
27
+ htmlgraph git-event checkout "$OLD_HEAD" "$NEW_HEAD" "$FLAG" &> /dev/null &
28
+ exit 0
@@ -0,0 +1,24 @@
1
+ #!/bin/bash
2
+ #
3
+ # HtmlGraph Post-Commit Hook
4
+ # Logs Git commit events for agent-agnostic continuity tracking
5
+ #
6
+
7
+ set +e
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
10
+ cd "$PROJECT_ROOT" || exit 0
11
+
12
+ if [ ! -d ".htmlgraph" ]; then
13
+ exit 0
14
+ fi
15
+
16
+ if ! command -v htmlgraph &> /dev/null; then
17
+ if command -v python3 &> /dev/null; then
18
+ python3 -m htmlgraph.git_events commit &> /dev/null &
19
+ fi
20
+ exit 0
21
+ fi
22
+
23
+ htmlgraph git-event commit &> /dev/null &
24
+ exit 0
@@ -0,0 +1,26 @@
1
+ #!/bin/bash
2
+ #
3
+ # HtmlGraph Post-Merge Hook
4
+ # Logs successful merges for continuity tracking
5
+ #
6
+
7
+ set +e
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
10
+ cd "$PROJECT_ROOT" || exit 0
11
+
12
+ if [ ! -d ".htmlgraph" ]; then
13
+ exit 0
14
+ fi
15
+
16
+ SQUASH_FLAG="$1"
17
+
18
+ if ! command -v htmlgraph &> /dev/null; then
19
+ if command -v python3 &> /dev/null; then
20
+ python3 -m htmlgraph.git_events merge "$SQUASH_FLAG" &> /dev/null &
21
+ fi
22
+ exit 0
23
+ fi
24
+
25
+ htmlgraph git-event merge "$SQUASH_FLAG" &> /dev/null &
26
+ exit 0
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env python3
2
+ import logging
3
+
4
+ logger = logging.getLogger(__name__)
5
+
6
+ """
7
+ PostToolUseFailure Hook - Automatic Error Tracking and Debug Spike Creation
8
+
9
+ This hook is triggered when tool executions fail, enabling:
10
+ - Error logging to .htmlgraph/errors.jsonl
11
+ - Pattern detection for recurring errors (3+ occurrences)
12
+ - Automatic debug spike creation for investigation
13
+ - Error context preservation for debugging
14
+
15
+ CRITICAL REQUIREMENTS:
16
+ - MUST exit with code 0 (exit 1 blocks Claude)
17
+ - MUST execute quickly (< 1 second)
18
+ - MUST use file-based output (not stdout)
19
+ - MUST handle all exceptions gracefully
20
+ """
21
+
22
+ import json
23
+ import os
24
+ import sys
25
+ from datetime import datetime
26
+ from pathlib import Path
27
+ from typing import Any
28
+
29
+
30
+ def run(hook_input: dict[str, Any]) -> dict[str, Any]:
31
+ """
32
+ Handle tool execution failures.
33
+
34
+ Args:
35
+ hook_input: Hook input containing:
36
+ - name: Tool name that failed
37
+ - result: Tool result (may contain error field)
38
+ - session_id: Current session ID
39
+
40
+ Returns:
41
+ Standard hook response: {"continue": True}
42
+ """
43
+ try:
44
+ # DEBUG: Log raw hook input to understand structure
45
+ debug_log = Path(".htmlgraph/hook-debug.jsonl")
46
+ debug_log.parent.mkdir(parents=True, exist_ok=True)
47
+ with open(debug_log, "a") as f:
48
+ f.write(
49
+ json.dumps(
50
+ {
51
+ "raw_input": hook_input,
52
+ "keys": list(hook_input.keys()),
53
+ "ts": datetime.now().isoformat(),
54
+ }
55
+ )
56
+ + "\n"
57
+ )
58
+
59
+ # Extract error information from PostToolUse hook format
60
+ # Official PostToolUse uses: tool_name, tool_response
61
+ # Custom hooks may use: name, result
62
+ tool_name = hook_input.get("tool_name") or hook_input.get("name", "unknown")
63
+ session_id = hook_input.get("session_id", "unknown")
64
+
65
+ # Error message can be in different places depending on tool
66
+ error_msg = "No error message"
67
+
68
+ # Check tool_response field first (official PostToolUse format)
69
+ # Then check result field (custom hook format)
70
+ result = hook_input.get("tool_response") or hook_input.get("result", {})
71
+ if isinstance(result, dict):
72
+ if "error" in result:
73
+ error_msg = result["error"]
74
+ elif "message" in result:
75
+ error_msg = result["message"]
76
+ elif isinstance(result, str):
77
+ # Sometimes the error is directly in the result as a string
78
+ error_msg = result
79
+
80
+ # Fallback: check top-level error field
81
+ if error_msg == "No error message" and "error" in hook_input:
82
+ error_msg = hook_input["error"]
83
+
84
+ # Last resort: stringify the result if it contains error indicators
85
+ if error_msg == "No error message" and result:
86
+ result_str = str(result).lower()
87
+ if any(
88
+ indicator in result_str
89
+ for indicator in ["error", "failed", "exception"]
90
+ ):
91
+ error_msg = str(result)[:500] # Truncate to 500 chars
92
+
93
+ # Track error in file-based storage
94
+ error_log = Path(".htmlgraph/errors.jsonl")
95
+ error_log.parent.mkdir(parents=True, exist_ok=True)
96
+
97
+ error_entry = {
98
+ "timestamp": datetime.now().isoformat(),
99
+ "tool": tool_name,
100
+ "error": error_msg,
101
+ "session_id": session_id,
102
+ }
103
+
104
+ with open(error_log, "a") as f:
105
+ f.write(json.dumps(error_entry) + "\n")
106
+
107
+ # Check for error patterns (same error 3+ times)
108
+ if should_create_debug_spike(tool_name, error_msg, error_log):
109
+ create_debug_spike(tool_name, error_msg, error_log)
110
+
111
+ # Return success (don't block Claude)
112
+ return {"continue": True}
113
+
114
+ except Exception as e:
115
+ # Never raise - log and continue
116
+ logger.warning(f"PostToolUseFailure hook error: {e}")
117
+ return {"continue": True}
118
+
119
+
120
+ def should_create_debug_spike(tool: str, error: str, log_path: Path) -> bool:
121
+ """
122
+ Check if this error has occurred 3+ times.
123
+
124
+ Args:
125
+ tool: Tool name that failed
126
+ error: Error message
127
+ log_path: Path to errors.jsonl
128
+
129
+ Returns:
130
+ True if error occurred 3+ times, False otherwise
131
+ """
132
+ if not log_path.exists():
133
+ return False
134
+
135
+ count = 0
136
+ # Use first 100 chars of error for pattern matching
137
+ error_signature = error[:100]
138
+
139
+ with open(log_path) as f:
140
+ for line in f:
141
+ try:
142
+ entry = json.loads(line)
143
+ if entry.get("tool") == tool and error_signature in entry.get(
144
+ "error", ""
145
+ ):
146
+ count += 1
147
+ if count >= 3:
148
+ return True
149
+ except Exception:
150
+ continue
151
+ return False
152
+
153
+
154
+ def create_debug_spike(tool: str, error: str, log_path: Path) -> None:
155
+ """
156
+ Auto-create debug spike for recurring error.
157
+
158
+ Args:
159
+ tool: Tool name that failed
160
+ error: Error message
161
+ log_path: Path to errors.jsonl
162
+ """
163
+ try:
164
+ from htmlgraph import SDK
165
+
166
+ sdk = SDK(agent="error-tracker")
167
+
168
+ # Get recent occurrences
169
+ occurrences = []
170
+ error_signature = error[:100]
171
+ with open(log_path) as f:
172
+ for line in f:
173
+ try:
174
+ entry = json.loads(line)
175
+ if entry.get("tool") == tool and error_signature in entry.get(
176
+ "error", ""
177
+ ):
178
+ occurrences.append(entry)
179
+ except Exception:
180
+ continue
181
+
182
+ # Check if spike already exists for this error
183
+ spike_marker = Path(".htmlgraph/.error-spikes.json")
184
+ existing_spikes = {}
185
+ if spike_marker.exists():
186
+ try:
187
+ with open(spike_marker) as f:
188
+ existing_spikes = json.load(f)
189
+ except Exception:
190
+ pass
191
+
192
+ # Create unique key for this error
193
+ error_key = f"{tool}:{error_signature}"
194
+ if error_key in existing_spikes:
195
+ # Already created spike for this error
196
+ return
197
+
198
+ # Create debug spike
199
+ spike = (
200
+ sdk.spikes.create(f"Recurring Error: {tool}")
201
+ .set_spike_type("technical")
202
+ .set_findings(
203
+ f"""
204
+ ## Recurring Tool Failure Detected
205
+
206
+ **Tool**: {tool}
207
+ **Occurrences**: {len(occurrences)}
208
+ **First Seen**: {occurrences[0]["timestamp"] if occurrences else "unknown"}
209
+ **Last Seen**: {occurrences[-1]["timestamp"] if occurrences else "unknown"}
210
+
211
+ ### Error Message
212
+ ```
213
+ {error}
214
+ ```
215
+
216
+ ### Recent Occurrences
217
+ {chr(10).join(f"- {o['timestamp']}: {o.get('session_id', 'unknown')}" for o in occurrences[-5:])}
218
+
219
+ ### Recommended Actions
220
+ 1. Review error message for root cause
221
+ 2. Check if this is a known issue in the codebase
222
+ 3. Search GitHub issues if Claude Code related
223
+ 4. Fix underlying issue or add error handling
224
+ 5. Test fix to ensure error doesn't recur
225
+
226
+ ### Debugging Resources
227
+ - `.htmlgraph/errors.jsonl` - Full error log
228
+ - `DEBUGGING.md` - Systematic debugging guide
229
+ - `/doctor` - System diagnostics
230
+ - `claude --debug` - Verbose output
231
+ """
232
+ )
233
+ .save()
234
+ )
235
+
236
+ # Record that we created a spike for this error
237
+ existing_spikes[error_key] = {
238
+ "spike_id": spike.id,
239
+ "created": datetime.now().isoformat(),
240
+ "occurrences": len(occurrences),
241
+ }
242
+ with open(spike_marker, "w") as f:
243
+ json.dump(existing_spikes, f, indent=2)
244
+
245
+ logger.warning(f"Created debug spike: {spike.id}")
246
+
247
+ except Exception as e:
248
+ logger.warning(f"Failed to create debug spike: {e}")
249
+
250
+
251
+ def main() -> None:
252
+ """Hook entry point for script wrapper."""
253
+ # Check environment override
254
+ if os.environ.get("HTMLGRAPH_DISABLE_TRACKING") == "1":
255
+ print(json.dumps({"continue": True}))
256
+ sys.exit(0)
257
+
258
+ # Read tool input from stdin
259
+ try:
260
+ hook_input = json.load(sys.stdin)
261
+ except json.JSONDecodeError:
262
+ hook_input = {}
263
+
264
+ # Run hook
265
+ result = run(hook_input)
266
+
267
+ # Output response
268
+ print(json.dumps(result))
269
+ sys.exit(0) # Always exit 0
270
+
271
+
272
+ if __name__ == "__main__":
273
+ main()