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,6 @@
1
+ {
2
+ "dismissed_at": null,
3
+ "dismissed_by": null,
4
+ "session_id": null,
5
+ "show_count": 121
6
+ }
@@ -0,0 +1,72 @@
1
+ {
2
+ "version": "1.0",
3
+ "updated": "2026-01-10T08:55:56.503507",
4
+ "agents": {
5
+ "claude": {
6
+ "id": "claude",
7
+ "name": "Claude",
8
+ "capabilities": [
9
+ "python",
10
+ "javascript",
11
+ "typescript",
12
+ "html",
13
+ "css",
14
+ "code-review",
15
+ "testing",
16
+ "documentation",
17
+ "debugging",
18
+ "refactoring",
19
+ "architecture",
20
+ "api-design"
21
+ ],
22
+ "max_parallel_tasks": 3,
23
+ "preferred_complexity": [
24
+ "low",
25
+ "medium",
26
+ "high",
27
+ "very-high"
28
+ ],
29
+ "active": true,
30
+ "metadata": {}
31
+ },
32
+ "gemini": {
33
+ "id": "gemini",
34
+ "name": "Gemini",
35
+ "capabilities": [
36
+ "python",
37
+ "data-analysis",
38
+ "documentation",
39
+ "testing",
40
+ "code-review",
41
+ "javascript"
42
+ ],
43
+ "max_parallel_tasks": 2,
44
+ "preferred_complexity": [
45
+ "low",
46
+ "medium",
47
+ "high"
48
+ ],
49
+ "active": true,
50
+ "metadata": {}
51
+ },
52
+ "codex": {
53
+ "id": "codex",
54
+ "name": "Codex",
55
+ "capabilities": [
56
+ "python",
57
+ "javascript",
58
+ "debugging",
59
+ "testing",
60
+ "code-generation",
61
+ "documentation"
62
+ ],
63
+ "max_parallel_tasks": 2,
64
+ "preferred_complexity": [
65
+ "low",
66
+ "medium"
67
+ ],
68
+ "active": true,
69
+ "metadata": {}
70
+ }
71
+ }
72
+ }
Binary file
@@ -0,0 +1,45 @@
1
+ """
2
+ HtmlGraph hooks package.
3
+
4
+ This package contains the hook logic for HtmlGraph tracking integration
5
+ with Claude Code and other AI coding assistants.
6
+
7
+ All hooks use a unified architecture:
8
+ - Logic lives in package modules (not scripts)
9
+ - Parallel execution where possible (asyncio.gather)
10
+ - Unified error handling and response format
11
+ - Easy testing via direct imports
12
+ - Deployed via package updates
13
+ """
14
+
15
+ from pathlib import Path
16
+
17
+ from htmlgraph.hooks.posttooluse import posttooluse_hook
18
+ from htmlgraph.hooks.pretooluse import pretooluse_hook
19
+ from htmlgraph.hooks.state_manager import (
20
+ DriftQueueManager,
21
+ ParentActivityTracker,
22
+ UserQueryEventTracker,
23
+ )
24
+
25
+ # Directory containing hook scripts
26
+ HOOKS_DIR = Path(__file__).parent
27
+
28
+ # Git hooks that can be installed
29
+ AVAILABLE_HOOKS = [
30
+ "pre-commit",
31
+ "post-commit",
32
+ "pre-push",
33
+ "post-checkout",
34
+ "post-merge",
35
+ ]
36
+
37
+ __all__ = [
38
+ "pretooluse_hook",
39
+ "posttooluse_hook",
40
+ "ParentActivityTracker",
41
+ "UserQueryEventTracker",
42
+ "DriftQueueManager",
43
+ "AVAILABLE_HOOKS",
44
+ "HOOKS_DIR",
45
+ ]
@@ -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,354 @@
1
+ import logging
2
+
3
+ logger = logging.getLogger(__name__)
4
+
5
+ """
6
+ CIGS PreToolUse Enforcer - Enhanced Orchestrator Enforcement with Escalation
7
+
8
+ Integrates the Computational Imperative Guidance System (CIGS) into the PreToolUse
9
+ hook for intelligent delegation enforcement with escalating guidance.
10
+
11
+ Architecture:
12
+ 1. Uses existing OrchestratorValidator for base classification
13
+ 2. Loads session violation count from ViolationTracker
14
+ 3. Classifies operation using CostCalculator
15
+ 4. Generates imperative message with escalation via ImperativeMessageGenerator
16
+ 5. Records violation if should_delegate=True
17
+ 6. Returns hookSpecificOutput with imperative message
18
+
19
+ Escalation Levels:
20
+ - Level 0 (0 violations): Guidance - informative, no cost shown
21
+ - Level 1 (1 violation): Imperative - commanding, includes cost
22
+ - Level 2 (2 violations): Final Warning - urgent, includes consequences
23
+ - Level 3 (3+ violations): Circuit Breaker - blocking, requires acknowledgment
24
+
25
+ Design Reference:
26
+ .htmlgraph/spikes/computational-imperative-guidance-system-design.md
27
+ Part 2: CIGS PreToolUse Hook Integration
28
+ Part 4: Imperative Message Generation
29
+ """
30
+
31
+ import json
32
+ import os
33
+ import sys
34
+ from pathlib import Path
35
+ from typing import Any
36
+
37
+ from htmlgraph.cigs.cost import CostCalculator
38
+ from htmlgraph.cigs.messaging import ImperativeMessageGenerator
39
+ from htmlgraph.cigs.tracker import ViolationTracker
40
+ from htmlgraph.hooks.orchestrator import is_allowed_orchestrator_operation
41
+ from htmlgraph.orchestrator_mode import OrchestratorModeManager
42
+
43
+
44
+ class CIGSPreToolEnforcer:
45
+ """
46
+ CIGS-enhanced PreToolUse enforcement with escalating imperative messages.
47
+
48
+ Integrates all CIGS components for comprehensive delegation enforcement.
49
+ """
50
+
51
+ # Tools that are ALWAYS allowed (orchestrator core)
52
+ ALWAYS_ALLOWED = {"Task", "AskUserQuestion", "TodoWrite"}
53
+
54
+ # Exploration tools that require delegation after first use
55
+ EXPLORATION_TOOLS = {"Read", "Grep", "Glob"}
56
+
57
+ # Implementation tools that always require delegation
58
+ IMPLEMENTATION_TOOLS = {"Edit", "Write", "NotebookEdit", "Delete"}
59
+
60
+ def __init__(self, graph_dir: Path | None = None):
61
+ """
62
+ Initialize CIGS PreToolUse enforcer.
63
+
64
+ Args:
65
+ graph_dir: Root directory for HtmlGraph (defaults to .htmlgraph)
66
+ """
67
+ if graph_dir is None:
68
+ graph_dir = self._find_graph_dir()
69
+
70
+ self.graph_dir = graph_dir
71
+ self.manager = OrchestratorModeManager(graph_dir)
72
+ self.cost_calculator = CostCalculator()
73
+ self.message_generator = ImperativeMessageGenerator()
74
+ self.tracker = ViolationTracker(graph_dir)
75
+
76
+ # Ensure session ID is set (detect from environment or use current session)
77
+ if self.tracker._session_id is None:
78
+ self.tracker.set_session_id(self._get_or_create_session_id())
79
+
80
+ def _find_graph_dir(self) -> Path:
81
+ """Find .htmlgraph directory starting from cwd."""
82
+ cwd = Path.cwd()
83
+ graph_dir = cwd / ".htmlgraph"
84
+
85
+ if not graph_dir.exists():
86
+ for parent in [cwd.parent, cwd.parent.parent, cwd.parent.parent.parent]:
87
+ candidate = parent / ".htmlgraph"
88
+ if candidate.exists():
89
+ graph_dir = candidate
90
+ break
91
+
92
+ return graph_dir
93
+
94
+ def enforce(self, tool: str, params: dict[str, Any]) -> dict[str, Any]:
95
+ """
96
+ Enforce CIGS delegation rules with escalating guidance.
97
+
98
+ Args:
99
+ tool: Tool name (Read, Edit, Bash, etc.)
100
+ params: Tool parameters
101
+
102
+ Returns:
103
+ Hook response dict in Claude Code standard format:
104
+ {
105
+ "hookSpecificOutput": {
106
+ "hookEventName": "PreToolUse",
107
+ "permissionDecision": "allow" | "deny",
108
+ "additionalContext": "...", # If allow with guidance
109
+ "permissionDecisionReason": "...", # If deny
110
+ }
111
+ }
112
+ """
113
+ # Check if orchestrator mode is enabled
114
+ if not self.manager.is_enabled():
115
+ return self._allow()
116
+
117
+ enforcement_level = self.manager.get_enforcement_level()
118
+
119
+ # ALWAYS ALLOWED tools pass through
120
+ if tool in self.ALWAYS_ALLOWED:
121
+ return self._allow()
122
+
123
+ # Check if SDK operation (always allowed)
124
+ if self._is_sdk_operation(tool, params):
125
+ return self._allow()
126
+
127
+ # Get session violation summary
128
+ summary = self.tracker.get_session_violations()
129
+ violation_count = summary.total_violations
130
+
131
+ # Check circuit breaker (3+ violations)
132
+ if violation_count >= 3 and enforcement_level == "strict":
133
+ return self._circuit_breaker(violation_count)
134
+
135
+ # Classify operation using existing orchestrator logic
136
+ is_allowed, reason, category = is_allowed_orchestrator_operation(tool, params)
137
+
138
+ # CIGS enforces stricter rules in strict mode:
139
+ # - Even "single lookups" should be delegated (exploration tools)
140
+ # - All implementation tools should be delegated
141
+ should_delegate = False
142
+ if enforcement_level == "strict":
143
+ if tool in self.EXPLORATION_TOOLS or tool in self.IMPLEMENTATION_TOOLS:
144
+ should_delegate = True
145
+ # Override is_allowed - CIGS wants delegation even for first use
146
+ is_allowed = False
147
+
148
+ # If orchestrator allows and CIGS doesn't override, proceed
149
+ if is_allowed and not should_delegate:
150
+ return self._allow()
151
+
152
+ # Operation should be delegated - classify with cost analysis
153
+ classification = self.cost_calculator.classify_operation(
154
+ tool=tool,
155
+ params=params,
156
+ is_exploration_sequence=self._is_exploration_sequence(tool),
157
+ )
158
+
159
+ # Generate imperative message with escalation
160
+ imperative_message = self.message_generator.generate(
161
+ tool=tool,
162
+ classification=classification,
163
+ violation_count=violation_count,
164
+ autonomy_level=enforcement_level,
165
+ )
166
+
167
+ # Record violation for session tracking
168
+ predicted_waste = classification.predicted_cost - classification.optimal_cost
169
+ self.tracker.record_violation(
170
+ tool=tool,
171
+ params=params,
172
+ classification=classification,
173
+ predicted_waste=predicted_waste,
174
+ )
175
+
176
+ # Return response based on enforcement level and escalation
177
+ if enforcement_level == "strict":
178
+ # STRICT mode - deny with imperative message
179
+ return {
180
+ "hookSpecificOutput": {
181
+ "hookEventName": "PreToolUse",
182
+ "permissionDecision": "deny",
183
+ "permissionDecisionReason": imperative_message,
184
+ }
185
+ }
186
+ else:
187
+ # GUIDANCE mode - allow but with strong message
188
+ return {
189
+ "hookSpecificOutput": {
190
+ "hookEventName": "PreToolUse",
191
+ "permissionDecision": "allow",
192
+ "additionalContext": imperative_message,
193
+ }
194
+ }
195
+
196
+ def _allow(self) -> dict[str, Any]:
197
+ """Return allow response."""
198
+ return {
199
+ "hookSpecificOutput": {
200
+ "hookEventName": "PreToolUse",
201
+ "permissionDecision": "allow",
202
+ }
203
+ }
204
+
205
+ def _circuit_breaker(self, violation_count: int) -> dict[str, Any]:
206
+ """Return circuit breaker blocking response."""
207
+ message = (
208
+ "🚨 CIRCUIT BREAKER TRIGGERED\n\n"
209
+ f"You have violated delegation rules {violation_count} times this session.\n\n"
210
+ "**Violations detected:**\n"
211
+ "- Direct execution instead of delegation\n"
212
+ "- Context waste on tactical operations\n"
213
+ "- Ignored imperative guidance messages\n\n"
214
+ "**REQUIRED:** Acknowledge violations before proceeding:\n"
215
+ "`uv run htmlgraph orchestrator acknowledge-violation`\n\n"
216
+ "**OR** Change enforcement settings:\n"
217
+ "- Disable: `uv run htmlgraph orchestrator disable`\n"
218
+ "- Guidance mode: `uv run htmlgraph orchestrator set-level guidance`\n"
219
+ "- Reset violations: `uv run htmlgraph orchestrator reset-violations`"
220
+ )
221
+
222
+ return {
223
+ "hookSpecificOutput": {
224
+ "hookEventName": "PreToolUse",
225
+ "permissionDecision": "deny",
226
+ "permissionDecisionReason": message,
227
+ }
228
+ }
229
+
230
+ def _is_sdk_operation(self, tool: str, params: dict[str, Any]) -> bool:
231
+ """Check if operation is an SDK operation (always allowed)."""
232
+ if tool != "Bash":
233
+ return False
234
+
235
+ command = params.get("command", "")
236
+
237
+ # Allow htmlgraph SDK commands
238
+ if command.startswith("uv run htmlgraph ") or command.startswith("htmlgraph "):
239
+ return True
240
+
241
+ # Allow git read-only commands
242
+ if command.startswith(("git status", "git diff", "git log")):
243
+ return True
244
+
245
+ # Allow SDK inline usage
246
+ if "from htmlgraph import" in command or "import htmlgraph" in command:
247
+ return True
248
+
249
+ return False
250
+
251
+ def _is_exploration_sequence(self, tool: str) -> bool:
252
+ """Check if this is part of an exploration sequence."""
253
+ if tool not in self.EXPLORATION_TOOLS:
254
+ return False
255
+
256
+ # Check recent history for exploration pattern
257
+ # This is simplified - could use tool_history from orchestrator.py
258
+ summary = self.tracker.get_session_violations()
259
+
260
+ # If we've already had exploration violations, this is a sequence
261
+ exploration_violations = [
262
+ v for v in summary.violations if v.tool in self.EXPLORATION_TOOLS
263
+ ]
264
+
265
+ return len(exploration_violations) >= 1
266
+
267
+ def _get_or_create_session_id(self) -> str:
268
+ """Get or create a session ID for tracking."""
269
+ # Try to get from environment
270
+ if "HTMLGRAPH_SESSION_ID" in os.environ:
271
+ return os.environ["HTMLGRAPH_SESSION_ID"]
272
+
273
+ # Try to get from session manager
274
+ try:
275
+ from htmlgraph.session_manager import SessionManager
276
+
277
+ sm = SessionManager(self.graph_dir)
278
+ current = sm.get_active_session()
279
+ if current:
280
+ return str(current.id)
281
+ except Exception:
282
+ pass
283
+
284
+ # Fallback: create a session ID for this test/run
285
+ # Use a consistent ID for the process
286
+ if not hasattr(self.__class__, "_fallback_session_id"):
287
+ from uuid import uuid4
288
+
289
+ fallback_id: str = f"test-session-{uuid4().hex[:8]}"
290
+ setattr(self.__class__, "_fallback_session_id", fallback_id)
291
+ return fallback_id
292
+
293
+ return str(getattr(self.__class__, "_fallback_session_id"))
294
+
295
+
296
+ def enforce_cigs_pretool(tool_input: dict[str, Any]) -> dict[str, Any]:
297
+ """
298
+ Main entry point for CIGS PreToolUse enforcement.
299
+
300
+ Args:
301
+ tool_input: Hook input with tool name and parameters
302
+
303
+ Returns:
304
+ Hook response dict in Claude Code standard format
305
+ """
306
+ # Extract tool and params from input
307
+ tool = tool_input.get("name", "") or tool_input.get("tool_name", "")
308
+ params = tool_input.get("input", {}) or tool_input.get("tool_input", {})
309
+
310
+ # Create enforcer and run
311
+ try:
312
+ enforcer = CIGSPreToolEnforcer()
313
+ return enforcer.enforce(tool, params)
314
+ except Exception as e:
315
+ # Graceful degradation - allow on error
316
+ logger.warning(f"Warning: CIGS enforcement error: {e}")
317
+ return {
318
+ "hookSpecificOutput": {
319
+ "hookEventName": "PreToolUse",
320
+ "permissionDecision": "allow",
321
+ }
322
+ }
323
+
324
+
325
+ def main() -> None:
326
+ """Hook entry point for script wrapper."""
327
+ # Check environment overrides
328
+ if os.environ.get("HTMLGRAPH_DISABLE_TRACKING") == "1":
329
+ print(json.dumps({"hookSpecificOutput": {"permissionDecision": "allow"}}))
330
+ sys.exit(0)
331
+
332
+ if os.environ.get("HTMLGRAPH_ORCHESTRATOR_DISABLED") == "1":
333
+ print(json.dumps({"hookSpecificOutput": {"permissionDecision": "allow"}}))
334
+ sys.exit(0)
335
+
336
+ # Read tool input from stdin
337
+ try:
338
+ tool_input = json.load(sys.stdin)
339
+ except json.JSONDecodeError:
340
+ tool_input = {}
341
+
342
+ # Run CIGS enforcement
343
+ result = enforce_cigs_pretool(tool_input)
344
+
345
+ # Output response
346
+ print(json.dumps(result))
347
+
348
+ # Exit code based on permission decision
349
+ permission = result.get("hookSpecificOutput", {}).get("permissionDecision", "allow")
350
+ sys.exit(0 if permission == "allow" else 1)
351
+
352
+
353
+ if __name__ == "__main__":
354
+ main()