htmlgraph 0.20.1__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 (304) 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 +51 -1
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +26 -10
  7. htmlgraph/agent_registry.py +2 -1
  8. htmlgraph/analytics/__init__.py +8 -1
  9. htmlgraph/analytics/cli.py +86 -20
  10. htmlgraph/analytics/cost_analyzer.py +391 -0
  11. htmlgraph/analytics/cost_monitor.py +664 -0
  12. htmlgraph/analytics/cost_reporter.py +675 -0
  13. htmlgraph/analytics/cross_session.py +617 -0
  14. htmlgraph/analytics/dependency.py +10 -6
  15. htmlgraph/analytics/pattern_learning.py +771 -0
  16. htmlgraph/analytics/session_graph.py +707 -0
  17. htmlgraph/analytics/strategic/__init__.py +80 -0
  18. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  19. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  20. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  21. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  22. htmlgraph/analytics/work_type.py +67 -27
  23. htmlgraph/analytics_index.py +53 -20
  24. htmlgraph/api/__init__.py +3 -0
  25. htmlgraph/api/cost_alerts_websocket.py +416 -0
  26. htmlgraph/api/main.py +2498 -0
  27. htmlgraph/api/static/htmx.min.js +1 -0
  28. htmlgraph/api/static/style-redesign.css +1344 -0
  29. htmlgraph/api/static/style.css +1079 -0
  30. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  31. htmlgraph/api/templates/dashboard.html +794 -0
  32. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  33. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  34. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  35. htmlgraph/api/templates/partials/agents.html +317 -0
  36. htmlgraph/api/templates/partials/event-traces.html +373 -0
  37. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  38. htmlgraph/api/templates/partials/features.html +578 -0
  39. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  40. htmlgraph/api/templates/partials/metrics.html +346 -0
  41. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  42. htmlgraph/api/templates/partials/orchestration.html +198 -0
  43. htmlgraph/api/templates/partials/spawners.html +375 -0
  44. htmlgraph/api/templates/partials/work-items.html +613 -0
  45. htmlgraph/api/websocket.py +538 -0
  46. htmlgraph/archive/__init__.py +24 -0
  47. htmlgraph/archive/bloom.py +234 -0
  48. htmlgraph/archive/fts.py +297 -0
  49. htmlgraph/archive/manager.py +583 -0
  50. htmlgraph/archive/search.py +244 -0
  51. htmlgraph/atomic_ops.py +560 -0
  52. htmlgraph/attribute_index.py +2 -1
  53. htmlgraph/bounded_paths.py +539 -0
  54. htmlgraph/builders/base.py +57 -2
  55. htmlgraph/builders/bug.py +19 -3
  56. htmlgraph/builders/chore.py +19 -3
  57. htmlgraph/builders/epic.py +19 -3
  58. htmlgraph/builders/feature.py +27 -3
  59. htmlgraph/builders/insight.py +2 -1
  60. htmlgraph/builders/metric.py +2 -1
  61. htmlgraph/builders/pattern.py +2 -1
  62. htmlgraph/builders/phase.py +19 -3
  63. htmlgraph/builders/spike.py +29 -3
  64. htmlgraph/builders/track.py +42 -1
  65. htmlgraph/cigs/__init__.py +81 -0
  66. htmlgraph/cigs/autonomy.py +385 -0
  67. htmlgraph/cigs/cost.py +475 -0
  68. htmlgraph/cigs/messages_basic.py +472 -0
  69. htmlgraph/cigs/messaging.py +365 -0
  70. htmlgraph/cigs/models.py +771 -0
  71. htmlgraph/cigs/pattern_storage.py +427 -0
  72. htmlgraph/cigs/patterns.py +503 -0
  73. htmlgraph/cigs/posttool_analyzer.py +234 -0
  74. htmlgraph/cigs/reporter.py +818 -0
  75. htmlgraph/cigs/tracker.py +317 -0
  76. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  77. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  78. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  79. htmlgraph/cli/__init__.py +42 -0
  80. htmlgraph/cli/__main__.py +6 -0
  81. htmlgraph/cli/analytics.py +1424 -0
  82. htmlgraph/cli/base.py +685 -0
  83. htmlgraph/cli/constants.py +206 -0
  84. htmlgraph/cli/core.py +954 -0
  85. htmlgraph/cli/main.py +147 -0
  86. htmlgraph/cli/models.py +475 -0
  87. htmlgraph/cli/templates/__init__.py +1 -0
  88. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  89. htmlgraph/cli/work/__init__.py +239 -0
  90. htmlgraph/cli/work/browse.py +115 -0
  91. htmlgraph/cli/work/features.py +568 -0
  92. htmlgraph/cli/work/orchestration.py +676 -0
  93. htmlgraph/cli/work/report.py +728 -0
  94. htmlgraph/cli/work/sessions.py +466 -0
  95. htmlgraph/cli/work/snapshot.py +559 -0
  96. htmlgraph/cli/work/tracks.py +486 -0
  97. htmlgraph/cli_commands/__init__.py +1 -0
  98. htmlgraph/cli_commands/feature.py +195 -0
  99. htmlgraph/cli_framework.py +115 -0
  100. htmlgraph/collections/__init__.py +2 -0
  101. htmlgraph/collections/base.py +197 -14
  102. htmlgraph/collections/bug.py +2 -1
  103. htmlgraph/collections/chore.py +2 -1
  104. htmlgraph/collections/epic.py +2 -1
  105. htmlgraph/collections/feature.py +2 -1
  106. htmlgraph/collections/insight.py +2 -1
  107. htmlgraph/collections/metric.py +2 -1
  108. htmlgraph/collections/pattern.py +2 -1
  109. htmlgraph/collections/phase.py +2 -1
  110. htmlgraph/collections/session.py +194 -0
  111. htmlgraph/collections/spike.py +13 -2
  112. htmlgraph/collections/task_delegation.py +241 -0
  113. htmlgraph/collections/todo.py +14 -1
  114. htmlgraph/collections/traces.py +487 -0
  115. htmlgraph/config/cost_models.json +56 -0
  116. htmlgraph/config.py +190 -0
  117. htmlgraph/context_analytics.py +2 -1
  118. htmlgraph/converter.py +116 -7
  119. htmlgraph/cost_analysis/__init__.py +5 -0
  120. htmlgraph/cost_analysis/analyzer.py +438 -0
  121. htmlgraph/dashboard.html +2246 -248
  122. htmlgraph/dashboard.html.backup +6592 -0
  123. htmlgraph/dashboard.html.bak +7181 -0
  124. htmlgraph/dashboard.html.bak2 +7231 -0
  125. htmlgraph/dashboard.html.bak3 +7232 -0
  126. htmlgraph/db/__init__.py +38 -0
  127. htmlgraph/db/queries.py +790 -0
  128. htmlgraph/db/schema.py +1788 -0
  129. htmlgraph/decorators.py +317 -0
  130. htmlgraph/dependency_models.py +2 -1
  131. htmlgraph/deploy.py +26 -27
  132. htmlgraph/docs/API_REFERENCE.md +841 -0
  133. htmlgraph/docs/HTTP_API.md +750 -0
  134. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  135. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  136. htmlgraph/docs/README.md +532 -0
  137. htmlgraph/docs/__init__.py +77 -0
  138. htmlgraph/docs/docs_version.py +55 -0
  139. htmlgraph/docs/metadata.py +93 -0
  140. htmlgraph/docs/migrations.py +232 -0
  141. htmlgraph/docs/template_engine.py +143 -0
  142. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  143. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  144. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  145. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  146. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  147. htmlgraph/docs/version_check.py +163 -0
  148. htmlgraph/edge_index.py +2 -1
  149. htmlgraph/error_handler.py +544 -0
  150. htmlgraph/event_log.py +86 -37
  151. htmlgraph/event_migration.py +2 -1
  152. htmlgraph/file_watcher.py +12 -8
  153. htmlgraph/find_api.py +2 -1
  154. htmlgraph/git_events.py +67 -9
  155. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  156. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  157. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  158. htmlgraph/hooks/__init__.py +8 -0
  159. htmlgraph/hooks/bootstrap.py +169 -0
  160. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  161. htmlgraph/hooks/concurrent_sessions.py +208 -0
  162. htmlgraph/hooks/context.py +350 -0
  163. htmlgraph/hooks/drift_handler.py +525 -0
  164. htmlgraph/hooks/event_tracker.py +790 -99
  165. htmlgraph/hooks/git_commands.py +175 -0
  166. htmlgraph/hooks/installer.py +5 -1
  167. htmlgraph/hooks/orchestrator.py +327 -76
  168. htmlgraph/hooks/orchestrator_reflector.py +31 -4
  169. htmlgraph/hooks/post_tool_use_failure.py +32 -7
  170. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  171. htmlgraph/hooks/posttooluse.py +92 -19
  172. htmlgraph/hooks/pretooluse.py +527 -7
  173. htmlgraph/hooks/prompt_analyzer.py +637 -0
  174. htmlgraph/hooks/session_handler.py +668 -0
  175. htmlgraph/hooks/session_summary.py +395 -0
  176. htmlgraph/hooks/state_manager.py +504 -0
  177. htmlgraph/hooks/subagent_detection.py +202 -0
  178. htmlgraph/hooks/subagent_stop.py +369 -0
  179. htmlgraph/hooks/task_enforcer.py +99 -4
  180. htmlgraph/hooks/validator.py +212 -91
  181. htmlgraph/ids.py +2 -1
  182. htmlgraph/learning.py +125 -100
  183. htmlgraph/mcp_server.py +2 -1
  184. htmlgraph/models.py +217 -18
  185. htmlgraph/operations/README.md +62 -0
  186. htmlgraph/operations/__init__.py +79 -0
  187. htmlgraph/operations/analytics.py +339 -0
  188. htmlgraph/operations/bootstrap.py +289 -0
  189. htmlgraph/operations/events.py +244 -0
  190. htmlgraph/operations/fastapi_server.py +231 -0
  191. htmlgraph/operations/hooks.py +350 -0
  192. htmlgraph/operations/initialization.py +597 -0
  193. htmlgraph/operations/initialization.py.backup +228 -0
  194. htmlgraph/operations/server.py +303 -0
  195. htmlgraph/orchestration/__init__.py +58 -0
  196. htmlgraph/orchestration/claude_launcher.py +179 -0
  197. htmlgraph/orchestration/command_builder.py +72 -0
  198. htmlgraph/orchestration/headless_spawner.py +281 -0
  199. htmlgraph/orchestration/live_events.py +377 -0
  200. htmlgraph/orchestration/model_selection.py +327 -0
  201. htmlgraph/orchestration/plugin_manager.py +140 -0
  202. htmlgraph/orchestration/prompts.py +137 -0
  203. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  204. htmlgraph/orchestration/spawners/__init__.py +16 -0
  205. htmlgraph/orchestration/spawners/base.py +194 -0
  206. htmlgraph/orchestration/spawners/claude.py +173 -0
  207. htmlgraph/orchestration/spawners/codex.py +435 -0
  208. htmlgraph/orchestration/spawners/copilot.py +294 -0
  209. htmlgraph/orchestration/spawners/gemini.py +471 -0
  210. htmlgraph/orchestration/subprocess_runner.py +36 -0
  211. htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
  212. htmlgraph/orchestration.md +563 -0
  213. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  214. htmlgraph/orchestrator.py +2 -1
  215. htmlgraph/orchestrator_config.py +357 -0
  216. htmlgraph/orchestrator_mode.py +115 -4
  217. htmlgraph/parallel.py +2 -1
  218. htmlgraph/parser.py +86 -6
  219. htmlgraph/path_query.py +608 -0
  220. htmlgraph/pattern_matcher.py +636 -0
  221. htmlgraph/pydantic_models.py +476 -0
  222. htmlgraph/quality_gates.py +350 -0
  223. htmlgraph/query_builder.py +2 -1
  224. htmlgraph/query_composer.py +509 -0
  225. htmlgraph/reflection.py +443 -0
  226. htmlgraph/refs.py +344 -0
  227. htmlgraph/repo_hash.py +512 -0
  228. htmlgraph/repositories/__init__.py +292 -0
  229. htmlgraph/repositories/analytics_repository.py +455 -0
  230. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  231. htmlgraph/repositories/feature_repository.py +581 -0
  232. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  233. htmlgraph/repositories/feature_repository_memory.py +607 -0
  234. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  235. htmlgraph/repositories/filter_service.py +620 -0
  236. htmlgraph/repositories/filter_service_standard.py +445 -0
  237. htmlgraph/repositories/shared_cache.py +621 -0
  238. htmlgraph/repositories/shared_cache_memory.py +395 -0
  239. htmlgraph/repositories/track_repository.py +552 -0
  240. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  241. htmlgraph/repositories/track_repository_memory.py +508 -0
  242. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  243. htmlgraph/sdk/__init__.py +398 -0
  244. htmlgraph/sdk/__init__.pyi +14 -0
  245. htmlgraph/sdk/analytics/__init__.py +19 -0
  246. htmlgraph/sdk/analytics/engine.py +155 -0
  247. htmlgraph/sdk/analytics/helpers.py +178 -0
  248. htmlgraph/sdk/analytics/registry.py +109 -0
  249. htmlgraph/sdk/base.py +484 -0
  250. htmlgraph/sdk/constants.py +216 -0
  251. htmlgraph/sdk/core.pyi +308 -0
  252. htmlgraph/sdk/discovery.py +120 -0
  253. htmlgraph/sdk/help/__init__.py +12 -0
  254. htmlgraph/sdk/help/mixin.py +699 -0
  255. htmlgraph/sdk/mixins/__init__.py +15 -0
  256. htmlgraph/sdk/mixins/attribution.py +113 -0
  257. htmlgraph/sdk/mixins/mixin.py +410 -0
  258. htmlgraph/sdk/operations/__init__.py +12 -0
  259. htmlgraph/sdk/operations/mixin.py +427 -0
  260. htmlgraph/sdk/orchestration/__init__.py +17 -0
  261. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  262. htmlgraph/sdk/orchestration/spawner.py +204 -0
  263. htmlgraph/sdk/planning/__init__.py +19 -0
  264. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  265. htmlgraph/sdk/planning/mixin.py +211 -0
  266. htmlgraph/sdk/planning/parallel.py +186 -0
  267. htmlgraph/sdk/planning/queue.py +210 -0
  268. htmlgraph/sdk/planning/recommendations.py +87 -0
  269. htmlgraph/sdk/planning/smart_planning.py +319 -0
  270. htmlgraph/sdk/session/__init__.py +19 -0
  271. htmlgraph/sdk/session/continuity.py +57 -0
  272. htmlgraph/sdk/session/handoff.py +110 -0
  273. htmlgraph/sdk/session/info.py +309 -0
  274. htmlgraph/sdk/session/manager.py +103 -0
  275. htmlgraph/sdk/strategic/__init__.py +26 -0
  276. htmlgraph/sdk/strategic/mixin.py +563 -0
  277. htmlgraph/server.py +295 -107
  278. htmlgraph/session_hooks.py +300 -0
  279. htmlgraph/session_manager.py +285 -3
  280. htmlgraph/session_registry.py +587 -0
  281. htmlgraph/session_state.py +436 -0
  282. htmlgraph/session_warning.py +2 -1
  283. htmlgraph/sessions/__init__.py +23 -0
  284. htmlgraph/sessions/handoff.py +756 -0
  285. htmlgraph/system_prompts.py +450 -0
  286. htmlgraph/templates/orchestration-view.html +350 -0
  287. htmlgraph/track_builder.py +33 -1
  288. htmlgraph/track_manager.py +38 -0
  289. htmlgraph/transcript.py +18 -5
  290. htmlgraph/validation.py +115 -0
  291. htmlgraph/watch.py +2 -1
  292. htmlgraph/work_type_utils.py +2 -1
  293. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
  294. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
  295. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  296. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  297. htmlgraph/cli.py +0 -4839
  298. htmlgraph/sdk.py +0 -2359
  299. htmlgraph-0.20.1.dist-info/RECORD +0 -118
  300. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  301. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  302. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  303. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  304. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
htmlgraph/file_watcher.py CHANGED
@@ -1,3 +1,7 @@
1
+ import logging
2
+
3
+ logger = logging.getLogger(__name__)
4
+
1
5
  """
2
6
  File watcher for automatic graph reloading.
3
7
 
@@ -67,7 +71,7 @@ class GraphFileHandler(FileSystemEventHandler):
67
71
 
68
72
  def _trigger_reload(self) -> None:
69
73
  """Trigger a reload after debounce delay."""
70
- print(f"[FileWatcher] Reloading collection: {self.collection}")
74
+ logger.info(f"[FileWatcher] Reloading collection: {self.collection}")
71
75
  self.reload_callback()
72
76
 
73
77
  def _debounced_reload(self) -> None:
@@ -87,7 +91,7 @@ class GraphFileHandler(FileSystemEventHandler):
87
91
  if not self._is_relevant_file(str(event.src_path)):
88
92
  return
89
93
 
90
- print(
94
+ logger.debug(
91
95
  f"[FileWatcher] {self.collection}: File created - {Path(str(event.src_path)).name}"
92
96
  )
93
97
  self._debounced_reload()
@@ -101,7 +105,7 @@ class GraphFileHandler(FileSystemEventHandler):
101
105
  if not self._is_relevant_file(str(event.src_path)):
102
106
  return
103
107
 
104
- print(
108
+ logger.debug(
105
109
  f"[FileWatcher] {self.collection}: File modified - {Path(str(event.src_path)).name}"
106
110
  )
107
111
  self._debounced_reload()
@@ -115,7 +119,7 @@ class GraphFileHandler(FileSystemEventHandler):
115
119
  if not self._is_relevant_file(str(event.src_path)):
116
120
  return
117
121
 
118
- print(
122
+ logger.debug(
119
123
  f"[FileWatcher] {self.collection}: File deleted - {Path(str(event.src_path)).name}"
120
124
  )
121
125
  self._debounced_reload()
@@ -146,7 +150,7 @@ class GraphWatcher:
146
150
 
147
151
  def start(self) -> None:
148
152
  """Start watching for file changes."""
149
- print(
153
+ logger.info(
150
154
  f"[FileWatcher] Starting file watcher for {len(self.collections)} collections..."
151
155
  )
152
156
 
@@ -160,7 +164,7 @@ class GraphWatcher:
160
164
  def reload() -> None:
161
165
  graph = self.get_graph_callback(coll)
162
166
  count = graph.reload()
163
- print(f"[FileWatcher] Reloaded {count} nodes in {coll}")
167
+ logger.info(f"[FileWatcher] Reloaded {count} nodes in {coll}")
164
168
 
165
169
  return reload
166
170
 
@@ -173,11 +177,11 @@ class GraphWatcher:
173
177
  self.observer.schedule(handler, str(collection_dir), recursive=recursive)
174
178
 
175
179
  self.observer.start()
176
- print(f"[FileWatcher] Watching {self.graph_dir} for changes...")
180
+ logger.info(f"[FileWatcher] Watching {self.graph_dir} for changes...")
177
181
 
178
182
  def stop(self) -> None:
179
183
  """Stop watching for file changes."""
180
- print("[FileWatcher] Stopping file watcher...")
184
+ logger.info("[FileWatcher] Stopping file watcher...")
181
185
  self.observer.stop()
182
186
  self.observer.join()
183
187
 
htmlgraph/find_api.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  BeautifulSoup-style Find API for HtmlGraph.
3
5
 
@@ -22,7 +24,6 @@ Example:
22
24
  nodes = graph.find_all(properties__effort__gt=8)
23
25
  """
24
26
 
25
- from __future__ import annotations
26
27
 
27
28
  import re
28
29
  from collections.abc import Callable
htmlgraph/git_events.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Git event logging for HtmlGraph.
3
5
 
@@ -10,7 +12,6 @@ Design goals:
10
12
  - Analytics-friendly: schema compatible with EventRecord/AnalyticsIndex
11
13
  """
12
14
 
13
- from __future__ import annotations
14
15
 
15
16
  import os
16
17
  import re
@@ -168,9 +169,19 @@ def parse_feature_refs(message: str) -> list[str]:
168
169
  Parse feature IDs from commit message.
169
170
 
170
171
  Looks for patterns like:
171
- - Implements: feature-xyz
172
+ - Implements: feat-xyz
172
173
  - Fixes: bug-abc
173
- - feature-xyz
174
+ - [feat-123abc]
175
+ - feat-xyz
176
+
177
+ Supports HtmlGraph ID formats:
178
+ - feat-XXXXXXXX (features)
179
+ - feature-XXXXXXXX (legacy features)
180
+ - bug-XXXXXXXX (bugs)
181
+ - spk-XXXXXXXX (spikes)
182
+ - spike-XXXXXXXX (legacy spikes)
183
+ - chr-XXXXXXXX (chores)
184
+ - trk-XXXXXXXX (tracks)
174
185
 
175
186
  Args:
176
187
  message: Commit message
@@ -180,14 +191,21 @@ def parse_feature_refs(message: str) -> list[str]:
180
191
  """
181
192
  features = []
182
193
 
183
- # Pattern: Implements: feature-xyz
184
- pattern1 = r"(?:Implements|Fixes|Closes|Refs):\s*(feature-[\w-]+|bug-[\w-]+)"
194
+ # All HtmlGraph ID prefixes (current + legacy)
195
+ id_prefixes = r"(?:feat|feature|bug|spk|spike|chr|chore|trk|track|todo)"
196
+
197
+ # Pattern 1: Explicit tags (Implements: feat-xyz)
198
+ pattern1 = rf"(?:Implements|Fixes|Closes|Refs):\s*({id_prefixes}-[\w-]+)"
185
199
  features.extend(re.findall(pattern1, message, re.IGNORECASE))
186
200
 
187
- # Pattern: feature-xyz (anywhere in message)
188
- pattern2 = r"\b(feature-[\w-]+|bug-[\w-]+)\b"
201
+ # Pattern 2: Square brackets [feat-xyz] (common in commit messages)
202
+ pattern2 = rf"\[({id_prefixes}-[\w-]+)\]"
189
203
  features.extend(re.findall(pattern2, message, re.IGNORECASE))
190
204
 
205
+ # Pattern 3: Anywhere in message as word boundary
206
+ pattern3 = rf"\b({id_prefixes}-[\w-]+)\b"
207
+ features.extend(re.findall(pattern3, message, re.IGNORECASE))
208
+
191
209
  # Remove duplicates while preserving order
192
210
  seen = set()
193
211
  unique_features = []
@@ -260,7 +278,10 @@ def _append_event(
260
278
  p.parent.mkdir(parents=True, exist_ok=True)
261
279
  with p.open("a", encoding="utf-8") as f:
262
280
  f.write(
263
- json.dumps(record.to_json(), ensure_ascii=False, default=str) + "\n"
281
+ json.dumps(
282
+ record.model_dump(mode="json"), ensure_ascii=False, default=str
283
+ )
284
+ + "\n"
264
285
  )
265
286
  return
266
287
 
@@ -288,7 +309,44 @@ def _determine_context(graph_dir: Path, commit_message: str | None = None) -> di
288
309
  if f and f not in all_features:
289
310
  all_features.append(f)
290
311
 
291
- session = get_active_session(graph_dir)
312
+ # Try to find the right session based on feature IDs in commit message
313
+ # This handles multi-agent scenarios where multiple sessions are active
314
+ session = None
315
+ if message_features:
316
+ # If commit mentions specific features, find the session working on them
317
+ manager = _get_session_manager(graph_dir)
318
+ if manager:
319
+ try:
320
+ # Try to find a session that has any of the message features as active
321
+ for feature_id in message_features:
322
+ # Get the feature to check which agent is working on it
323
+ try:
324
+ # Try features graph first
325
+ feature = manager.features_graph.get(feature_id)
326
+ if not feature:
327
+ # Try bugs graph
328
+ feature = manager.bugs_graph.get(feature_id)
329
+
330
+ if (
331
+ feature
332
+ and hasattr(feature, "agent_assigned")
333
+ and feature.agent_assigned
334
+ ):
335
+ # Find active session for this agent
336
+ session = manager.get_active_session(
337
+ agent=feature.agent_assigned
338
+ )
339
+ if session:
340
+ break
341
+ except Exception:
342
+ pass
343
+ except Exception:
344
+ pass
345
+
346
+ # Fallback to any active session if we couldn't match by feature
347
+ if not session:
348
+ session = get_active_session(graph_dir)
349
+
292
350
  if session:
293
351
  return {
294
352
  "session_id": session.id,
@@ -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
@@ -16,6 +16,11 @@ from pathlib import Path
16
16
 
17
17
  from htmlgraph.hooks.posttooluse import posttooluse_hook
18
18
  from htmlgraph.hooks.pretooluse import pretooluse_hook
19
+ from htmlgraph.hooks.state_manager import (
20
+ DriftQueueManager,
21
+ ParentActivityTracker,
22
+ UserQueryEventTracker,
23
+ )
19
24
 
20
25
  # Directory containing hook scripts
21
26
  HOOKS_DIR = Path(__file__).parent
@@ -32,6 +37,9 @@ AVAILABLE_HOOKS = [
32
37
  __all__ = [
33
38
  "pretooluse_hook",
34
39
  "posttooluse_hook",
40
+ "ParentActivityTracker",
41
+ "UserQueryEventTracker",
42
+ "DriftQueueManager",
35
43
  "AVAILABLE_HOOKS",
36
44
  "HOOKS_DIR",
37
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
+ ]