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
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,17 +12,19 @@ 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
17
18
  import subprocess
18
19
  from datetime import datetime
19
20
  from pathlib import Path
20
- from typing import Optional
21
+ from typing import TYPE_CHECKING, Any, cast
21
22
 
22
23
  from htmlgraph.event_log import EventRecord, JsonlEventLog
23
24
 
25
+ if TYPE_CHECKING:
26
+ from htmlgraph.session_manager import SessionManager
27
+
24
28
 
25
29
  def get_git_info() -> dict:
26
30
  """
@@ -33,85 +37,81 @@ def get_git_info() -> dict:
33
37
  try:
34
38
  # Get commit hash
35
39
  commit_hash = subprocess.check_output(
36
- ['git', 'rev-parse', 'HEAD'],
37
- stderr=subprocess.DEVNULL,
38
- text=True
40
+ ["git", "rev-parse", "HEAD"], stderr=subprocess.DEVNULL, text=True
39
41
  ).strip()
40
42
 
41
43
  commit_hash_short = subprocess.check_output(
42
- ['git', 'rev-parse', '--short', 'HEAD'],
44
+ ["git", "rev-parse", "--short", "HEAD"],
43
45
  stderr=subprocess.DEVNULL,
44
- text=True
46
+ text=True,
45
47
  ).strip()
46
48
 
47
49
  # Get branch name
48
50
  branch = subprocess.check_output(
49
- ['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
51
+ ["git", "rev-parse", "--abbrev-ref", "HEAD"],
50
52
  stderr=subprocess.DEVNULL,
51
- text=True
53
+ text=True,
52
54
  ).strip()
53
55
 
54
56
  # Get author info
55
57
  author_name = subprocess.check_output(
56
- ['git', 'log', '-1', '--format=%an'],
57
- stderr=subprocess.DEVNULL,
58
- text=True
58
+ ["git", "log", "-1", "--format=%an"], stderr=subprocess.DEVNULL, text=True
59
59
  ).strip()
60
60
 
61
61
  author_email = subprocess.check_output(
62
- ['git', 'log', '-1', '--format=%ae'],
63
- stderr=subprocess.DEVNULL,
64
- text=True
62
+ ["git", "log", "-1", "--format=%ae"], stderr=subprocess.DEVNULL, text=True
65
63
  ).strip()
66
64
 
67
65
  # Get commit message
68
66
  commit_message = subprocess.check_output(
69
- ['git', 'log', '-1', '--format=%B'],
70
- stderr=subprocess.DEVNULL,
71
- text=True
67
+ ["git", "log", "-1", "--format=%B"], stderr=subprocess.DEVNULL, text=True
72
68
  ).strip()
73
69
 
74
70
  # Get changed files
75
- files_changed = subprocess.check_output(
76
- ['git', 'diff-tree', '--no-commit-id', '--name-only', '-r', 'HEAD'],
77
- stderr=subprocess.DEVNULL,
78
- text=True
79
- ).strip().split('\n')
71
+ files_changed = (
72
+ subprocess.check_output(
73
+ ["git", "diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"],
74
+ stderr=subprocess.DEVNULL,
75
+ text=True,
76
+ )
77
+ .strip()
78
+ .split("\n")
79
+ )
80
80
  files_changed = [f for f in files_changed if f] # Remove empty strings
81
81
 
82
82
  # Get stats (insertions/deletions)
83
83
  stats = subprocess.check_output(
84
- ['git', 'diff-tree', '--no-commit-id', '--numstat', '-r', 'HEAD'],
84
+ ["git", "diff-tree", "--no-commit-id", "--numstat", "-r", "HEAD"],
85
85
  stderr=subprocess.DEVNULL,
86
- text=True
86
+ text=True,
87
87
  ).strip()
88
88
 
89
89
  insertions = 0
90
90
  deletions = 0
91
- for line in stats.split('\n'):
91
+ for line in stats.split("\n"):
92
92
  if line:
93
- parts = line.split('\t')
94
- if len(parts) >= 2 and parts[0] != '-' and parts[1] != '-':
93
+ parts = line.split("\t")
94
+ if len(parts) >= 2 and parts[0] != "-" and parts[1] != "-":
95
95
  insertions += int(parts[0])
96
96
  deletions += int(parts[1])
97
97
 
98
98
  return {
99
- 'commit_hash': commit_hash,
100
- 'commit_hash_short': commit_hash_short,
101
- 'branch': branch,
102
- 'author_name': author_name,
103
- 'author_email': author_email,
104
- 'commit_message': commit_message,
105
- 'files_changed': files_changed,
106
- 'insertions': insertions,
107
- 'deletions': deletions,
99
+ "commit_hash": commit_hash,
100
+ "commit_hash_short": commit_hash_short,
101
+ "branch": branch,
102
+ "author_name": author_name,
103
+ "author_email": author_email,
104
+ "commit_message": commit_message,
105
+ "files_changed": files_changed,
106
+ "insertions": insertions,
107
+ "deletions": deletions,
108
108
  }
109
109
 
110
110
  except subprocess.CalledProcessError:
111
111
  return {}
112
112
 
113
113
 
114
- def _get_session_manager(graph_dir: str | Path) -> "SessionManager | None":
114
+ def _get_session_manager(graph_dir: str | Path) -> SessionManager | None:
115
115
  try:
116
116
  from htmlgraph.session_manager import SessionManager
117
117
 
@@ -148,7 +148,7 @@ def get_primary_feature_id(graph_dir: str | Path = ".htmlgraph") -> str | None:
148
148
  return None
149
149
 
150
150
 
151
- def get_active_session(graph_dir: str | Path = ".htmlgraph"):
151
+ def get_active_session(graph_dir: str | Path = ".htmlgraph") -> Any:
152
152
  """
153
153
  Get the current active HtmlGraph session.
154
154
 
@@ -169,9 +169,19 @@ def parse_feature_refs(message: str) -> list[str]:
169
169
  Parse feature IDs from commit message.
170
170
 
171
171
  Looks for patterns like:
172
- - Implements: feature-xyz
172
+ - Implements: feat-xyz
173
173
  - Fixes: bug-abc
174
- - 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)
175
185
 
176
186
  Args:
177
187
  message: Commit message
@@ -181,14 +191,21 @@ def parse_feature_refs(message: str) -> list[str]:
181
191
  """
182
192
  features = []
183
193
 
184
- # Pattern: Implements: feature-xyz
185
- 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-]+)"
186
199
  features.extend(re.findall(pattern1, message, re.IGNORECASE))
187
200
 
188
- # Pattern: feature-xyz (anywhere in message)
189
- 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-]+)\]"
190
203
  features.extend(re.findall(pattern2, message, re.IGNORECASE))
191
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
+
192
209
  # Remove duplicates while preserving order
193
210
  seen = set()
194
211
  unique_features = []
@@ -200,7 +217,9 @@ def parse_feature_refs(message: str) -> list[str]:
200
217
  return unique_features
201
218
 
202
219
 
203
- def _parse_checkout_from_reflog(reflog_action: str | None) -> tuple[str | None, str | None]:
220
+ def _parse_checkout_from_reflog(
221
+ reflog_action: str | None,
222
+ ) -> tuple[str | None, str | None]:
204
223
  if not reflog_action:
205
224
  return None, None
206
225
  # Example: "checkout: moving from main to feature/foo"
@@ -228,6 +247,7 @@ def _append_event(
228
247
  ) -> None:
229
248
  # Auto-infer work type from feature_id (Phase 1: Work Type Classification)
230
249
  from htmlgraph.work_type_utils import infer_work_type_from_id
250
+
231
251
  work_type = infer_work_type_from_id(feature_id)
232
252
 
233
253
  record = EventRecord(
@@ -257,7 +277,12 @@ def _append_event(
257
277
  p = Path(override_path)
258
278
  p.parent.mkdir(parents=True, exist_ok=True)
259
279
  with p.open("a", encoding="utf-8") as f:
260
- f.write(json.dumps(record.to_json(), ensure_ascii=False, default=str) + "\n")
280
+ f.write(
281
+ json.dumps(
282
+ record.model_dump(mode="json"), ensure_ascii=False, default=str
283
+ )
284
+ + "\n"
285
+ )
261
286
  return
262
287
 
263
288
  log = JsonlEventLog(graph_dir / "events")
@@ -275,14 +300,53 @@ def _determine_context(graph_dir: Path, commit_message: str | None = None) -> di
275
300
  """
276
301
  active_features = get_active_features(graph_dir)
277
302
  primary_feature_id = get_primary_feature_id(graph_dir)
278
- message_features = parse_feature_refs(commit_message or "") if commit_message else []
303
+ message_features = (
304
+ parse_feature_refs(commit_message or "") if commit_message else []
305
+ )
279
306
 
280
307
  all_features: list[str] = []
281
- for f in (active_features + message_features):
308
+ for f in active_features + message_features:
282
309
  if f and f not in all_features:
283
310
  all_features.append(f)
284
311
 
285
- 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
+
286
350
  if session:
287
351
  return {
288
352
  "session_id": session.id,
@@ -341,14 +405,22 @@ def log_git_commit(graph_dir: str | Path = ".htmlgraph") -> bool:
341
405
  except Exception:
342
406
  parents = []
343
407
 
344
- ctx = _determine_context(graph_dir_path, commit_message=git_info.get("commit_message"))
408
+ ctx = _determine_context(
409
+ graph_dir_path, commit_message=git_info.get("commit_message")
410
+ )
345
411
  all_features: list[str] = ctx["all_features"]
346
412
 
347
413
  # Create one event per feature (to keep continuity queries simple).
348
414
  # If there are no features, write a single un-attributed event.
349
- feature_ids = all_features or [None]
415
+ feature_ids: list[str | None] = (
416
+ cast(list[str | None], all_features) if all_features else [None]
417
+ )
350
418
 
351
- subject = (git_info.get("commit_message") or "").strip().splitlines()[0] if git_info.get("commit_message") else ""
419
+ subject = (
420
+ (git_info.get("commit_message") or "").strip().splitlines()[0]
421
+ if git_info.get("commit_message")
422
+ else ""
423
+ )
352
424
  base_event_id = f"git-commit-{git_info['commit_hash']}"
353
425
 
354
426
  for fid in feature_ids:
@@ -394,8 +466,8 @@ def log_git_commit(graph_dir: str | Path = ".htmlgraph") -> bool:
394
466
  except Exception as e:
395
467
  # Never fail - just log error and continue
396
468
  try:
397
- error_log = Path('.htmlgraph/git-hook-errors.log')
398
- with open(error_log, 'a') as f:
469
+ error_log = Path(".htmlgraph/git-hook-errors.log")
470
+ with open(error_log, "a") as f:
399
471
  f.write(f"{datetime.now().isoformat()} - Error logging commit: {e}\n")
400
472
  except:
401
473
  pass
@@ -429,7 +501,9 @@ def log_git_checkout(
429
501
 
430
502
  # Always record, but callers can filter later based on flag.
431
503
  ctx = _determine_context(graph_dir_path)
432
- fid = ctx["primary_feature_id"] or (ctx["all_features"][0] if ctx["all_features"] else None)
504
+ fid = ctx["primary_feature_id"] or (
505
+ ctx["all_features"][0] if ctx["all_features"] else None
506
+ )
433
507
 
434
508
  event_id = f"git-checkout-{now.strftime('%Y%m%d%H%M%S')}-{(new_head or 'unknown')[:12]}"
435
509
  summary = "Checkout"
@@ -509,9 +583,13 @@ def log_git_merge(
509
583
  reflog_action = os.environ.get("GIT_REFLOG_ACTION")
510
584
 
511
585
  ctx = _determine_context(graph_dir_path)
512
- fid = ctx["primary_feature_id"] or (ctx["all_features"][0] if ctx["all_features"] else None)
586
+ fid = ctx["primary_feature_id"] or (
587
+ ctx["all_features"][0] if ctx["all_features"] else None
588
+ )
513
589
 
514
- event_id = f"git-merge-{now.strftime('%Y%m%d%H%M%S')}-{(new_head or 'unknown')[:12]}"
590
+ event_id = (
591
+ f"git-merge-{now.strftime('%Y%m%d%H%M%S')}-{(new_head or 'unknown')[:12]}"
592
+ )
515
593
  payload = {
516
594
  "type": "GitMerge",
517
595
  "squash": bool(squash_int),
@@ -574,7 +652,9 @@ def log_git_push(
574
652
  )
575
653
 
576
654
  ctx = _determine_context(graph_dir_path)
577
- fid = ctx["primary_feature_id"] or (ctx["all_features"][0] if ctx["all_features"] else None)
655
+ fid = ctx["primary_feature_id"] or (
656
+ ctx["all_features"][0] if ctx["all_features"] else None
657
+ )
578
658
 
579
659
  event_id = f"git-push-{now.strftime('%Y%m%d%H%M%S')}-{os.getpid()}"
580
660
  payload = {
@@ -606,17 +686,19 @@ def log_git_push(
606
686
  return False
607
687
 
608
688
 
609
- def main():
689
+ def main() -> None:
610
690
  """CLI entry point for git hook."""
611
691
  import sys
612
692
 
613
693
  if len(sys.argv) < 2:
614
- print("Usage: python -m htmlgraph.git_events <commit|checkout|merge|push> [args...]")
694
+ print(
695
+ "Usage: python -m htmlgraph.git_events <commit|checkout|merge|push> [args...]"
696
+ )
615
697
  sys.exit(1)
616
698
 
617
699
  event_type = sys.argv[1]
618
700
 
619
- if event_type == 'commit':
701
+ if event_type == "commit":
620
702
  sys.exit(0 if log_git_commit() else 1)
621
703
 
622
704
  if event_type == "checkout":
@@ -639,5 +721,5 @@ def main():
639
721
  sys.exit(1)
640
722
 
641
723
 
642
- if __name__ == '__main__':
724
+ if __name__ == "__main__":
643
725
  main()