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,10 @@
1
+ """
2
+ Services package for SessionManager functionality decomposition.
3
+
4
+ This package contains service classes that handle specific domains of functionality
5
+ previously embedded in the SessionManager god object.
6
+ """
7
+
8
+ from .claiming import ClaimingService
9
+
10
+ __all__ = ["ClaimingService"]
@@ -0,0 +1,199 @@
1
+ """
2
+ ClaimingService - Handles feature claiming and release logic.
3
+
4
+ Extracted from SessionManager to reduce complexity and improve maintainability.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from typing import TYPE_CHECKING
9
+
10
+ from htmlgraph.graph import HtmlGraph
11
+ from htmlgraph.models import Node
12
+
13
+ if TYPE_CHECKING:
14
+ from htmlgraph.session_manager import SessionManager
15
+
16
+
17
+ class ClaimingService:
18
+ """
19
+ Service for managing feature claims and releases.
20
+
21
+ This service handles:
22
+ - Claiming features for agents
23
+ - Releasing feature claims
24
+ - Auto-releasing all claims for an agent
25
+ - Releasing all claims for a session
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ features_graph: HtmlGraph,
31
+ bugs_graph: HtmlGraph,
32
+ session_manager: "SessionManager",
33
+ ):
34
+ """
35
+ Initialize ClaimingService.
36
+
37
+ Args:
38
+ features_graph: Graph for features collection
39
+ bugs_graph: Graph for bugs collection
40
+ session_manager: Reference to SessionManager for session operations
41
+ """
42
+ self.features_graph = features_graph
43
+ self.bugs_graph = bugs_graph
44
+ self.session_manager = session_manager
45
+
46
+ def _get_graph(self, collection: str) -> HtmlGraph:
47
+ """Get graph for a collection."""
48
+ if collection == "bugs":
49
+ return self.bugs_graph
50
+ return self.features_graph
51
+
52
+ def _invalidate_features_cache(self) -> None:
53
+ """Invalidate the active features cache in SessionManager."""
54
+ self.session_manager._features_cache_dirty = True
55
+
56
+ def claim_feature(
57
+ self,
58
+ feature_id: str,
59
+ collection: str = "features",
60
+ *,
61
+ agent: str,
62
+ ) -> Node | None:
63
+ """
64
+ Claim a feature for an agent.
65
+
66
+ Args:
67
+ feature_id: Feature to claim
68
+ collection: Collection name
69
+ agent: Agent name claiming the feature
70
+
71
+ Returns:
72
+ Updated Node or None
73
+ """
74
+ graph = self._get_graph(collection)
75
+ node = graph.get(feature_id)
76
+ if not node:
77
+ return None
78
+
79
+ # Check if already claimed by someone else
80
+ if node.agent_assigned and node.agent_assigned != agent:
81
+ # Check if session that claimed it is still active
82
+ if node.claimed_by_session:
83
+ session = self.session_manager.get_session(node.claimed_by_session)
84
+ if session and session.status == "active":
85
+ raise ValueError(
86
+ f"Feature '{feature_id}' is already claimed by {node.agent_assigned} "
87
+ f"(session {node.claimed_by_session})"
88
+ )
89
+
90
+ session = self.session_manager._ensure_session_for_agent(agent)
91
+
92
+ node.agent_assigned = agent
93
+ node.claimed_at = datetime.now()
94
+ node.claimed_by_session = session.id
95
+ node.updated = datetime.now()
96
+ graph.update(node)
97
+
98
+ self.session_manager._maybe_log_work_item_action(
99
+ agent=agent,
100
+ tool="FeatureClaim",
101
+ summary=f"Claimed: {collection}/{feature_id}",
102
+ feature_id=feature_id,
103
+ payload={"collection": collection, "action": "claim"},
104
+ )
105
+
106
+ return node
107
+
108
+ def release_feature(
109
+ self,
110
+ feature_id: str,
111
+ collection: str = "features",
112
+ *,
113
+ agent: str,
114
+ ) -> Node | None:
115
+ """
116
+ Release a feature claim.
117
+
118
+ Args:
119
+ feature_id: Feature to release
120
+ collection: Collection name
121
+ agent: Agent name releasing the feature
122
+
123
+ Returns:
124
+ Updated Node or None
125
+ """
126
+ graph = self._get_graph(collection)
127
+ node = graph.get(feature_id)
128
+ if not node:
129
+ return None
130
+
131
+ if node.agent_assigned and node.agent_assigned != agent:
132
+ raise ValueError(
133
+ f"Feature '{feature_id}' is claimed by {node.agent_assigned}, not {agent}"
134
+ )
135
+
136
+ node.agent_assigned = None
137
+ node.claimed_at = None
138
+ node.claimed_by_session = None
139
+ node.updated = datetime.now()
140
+ graph.update(node)
141
+
142
+ # Invalidate active features cache
143
+ self._invalidate_features_cache()
144
+
145
+ self.session_manager._maybe_log_work_item_action(
146
+ agent=agent,
147
+ tool="FeatureRelease",
148
+ summary=f"Released: {collection}/{feature_id}",
149
+ feature_id=feature_id,
150
+ payload={"collection": collection, "action": "release"},
151
+ )
152
+
153
+ return node
154
+
155
+ def auto_release_features(self, agent: str) -> list[str]:
156
+ """
157
+ Release all features claimed by an agent.
158
+
159
+ Args:
160
+ agent: Agent name
161
+
162
+ Returns:
163
+ List of released feature IDs
164
+ """
165
+ released = []
166
+ for collection in ["features", "bugs"]:
167
+ graph = self._get_graph(collection)
168
+ for node in graph:
169
+ if node.agent_assigned == agent:
170
+ node.agent_assigned = None
171
+ node.claimed_at = None
172
+ node.claimed_by_session = None
173
+ node.updated = datetime.now()
174
+ graph.update(node)
175
+ released.append(node.id)
176
+ return released
177
+
178
+ def release_session_features(self, session_id: str) -> list[str]:
179
+ """
180
+ Release all features claimed by a specific session.
181
+
182
+ Args:
183
+ session_id: Session ID
184
+
185
+ Returns:
186
+ List of released feature IDs
187
+ """
188
+ released = []
189
+ for collection in ["features", "bugs"]:
190
+ graph = self._get_graph(collection)
191
+ for node in graph:
192
+ if node.claimed_by_session == session_id:
193
+ node.agent_assigned = None
194
+ node.claimed_at = None
195
+ node.claimed_by_session = None
196
+ node.updated = datetime.now()
197
+ graph.update(node)
198
+ released.append(node.id)
199
+ return released
@@ -0,0 +1,300 @@
1
+ """
2
+ SessionStart Hook Integration - Initialize session registry with repo awareness.
3
+
4
+ Integrates:
5
+ - SessionRegistry: File-based session tracking
6
+ - RepoHash: Git awareness and repository identification
7
+ - AtomicFileWriter: Crash-safe writes
8
+
9
+ Called by SessionStart hook to:
10
+ 1. Register new session with repo info
11
+ 2. Export session IDs to CLAUDE_ENV_FILE
12
+ 3. Detect parent sessions from environment
13
+ 4. Initialize heartbeat mechanism
14
+ """
15
+
16
+ import logging
17
+ import os
18
+ import uuid
19
+ from datetime import datetime, timezone
20
+ from pathlib import Path
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def initialize_session_from_hook(env_file: str | None = None) -> str:
26
+ """
27
+ Initialize session registry from SessionStart hook.
28
+
29
+ Called automatically by SessionStart hook to set up session tracking.
30
+ Registers session with repository information and exports environment variables.
31
+
32
+ Args:
33
+ env_file: Path to CLAUDE_ENV_FILE (from hook environment).
34
+ Allows exporting session IDs to parent process.
35
+
36
+ Returns:
37
+ session_id: Generated session ID for logging/tracking
38
+
39
+ Raises:
40
+ OSError: If registry initialization fails
41
+ RuntimeError: If session registration fails
42
+ """
43
+ from htmlgraph.repo_hash import RepoHash
44
+ from htmlgraph.session_registry import SessionRegistry
45
+
46
+ try:
47
+ # Initialize registry (creates .htmlgraph/sessions/registry structure)
48
+ registry = SessionRegistry()
49
+ logger.debug(f"Initialized SessionRegistry at {registry.registry_dir}")
50
+
51
+ # Get repo information
52
+ try:
53
+ repo_hash = RepoHash()
54
+ git_info = repo_hash.get_git_info()
55
+ repo_hash_value = repo_hash.compute_repo_hash()
56
+
57
+ repo_info = {
58
+ "path": str(repo_hash.repo_path),
59
+ "hash": repo_hash_value,
60
+ "branch": git_info.get("branch"),
61
+ "commit": git_info.get("commit"),
62
+ "remote": git_info.get("remote"),
63
+ "dirty": git_info.get("dirty", False),
64
+ "is_monorepo": repo_hash.is_monorepo(),
65
+ "monorepo_project": repo_hash.get_monorepo_project(),
66
+ }
67
+ logger.debug(f"Repo info: {repo_info}")
68
+ except OSError as e:
69
+ logger.warning(f"Failed to get repo info: {e}")
70
+ repo_info = {
71
+ "path": str(Path.cwd()),
72
+ "hash": "unknown",
73
+ "branch": None,
74
+ "commit": None,
75
+ "remote": None,
76
+ "dirty": False,
77
+ "is_monorepo": False,
78
+ "monorepo_project": None,
79
+ }
80
+
81
+ # Get instance information
82
+ instance_info = {
83
+ "pid": os.getpid(),
84
+ "hostname": _get_hostname(),
85
+ "start_time": _get_utc_timestamp(),
86
+ }
87
+
88
+ # Generate session ID
89
+ session_id = f"sess-{uuid.uuid4().hex[:8]}"
90
+
91
+ # Register session atomically
92
+ try:
93
+ registry_file = registry.register_session(
94
+ session_id=session_id,
95
+ repo_info=repo_info,
96
+ instance_info=instance_info,
97
+ )
98
+ logger.info(f"Registered session {session_id} at {registry_file}")
99
+ except OSError as e:
100
+ logger.error(f"Failed to register session: {e}")
101
+ raise RuntimeError(f"Session registration failed: {e}") from e
102
+
103
+ # Export to CLAUDE_ENV_FILE if provided
104
+ if env_file:
105
+ try:
106
+ _export_to_env_file(
107
+ env_file=env_file,
108
+ session_id=session_id,
109
+ instance_id=registry.get_instance_id(),
110
+ repo_hash=repo_hash_value
111
+ if "repo_hash_value" in locals()
112
+ else "unknown",
113
+ )
114
+ logger.debug(f"Exported session environment to {env_file}")
115
+ except OSError as e:
116
+ logger.warning(f"Failed to export environment: {e}")
117
+ # Don't fail - session is registered even if export fails
118
+
119
+ # Check for parent session
120
+ parent_session_id = _get_parent_session_id()
121
+ if parent_session_id:
122
+ logger.info(f"Parent session detected: {parent_session_id}")
123
+ # Store parent relationship in environment for subprocesses
124
+ os.environ["HTMLGRAPH_PARENT_SESSION_ID"] = parent_session_id
125
+
126
+ return session_id
127
+
128
+ except Exception as e:
129
+ logger.error(f"Failed to initialize session: {e}", exc_info=True)
130
+ raise
131
+
132
+
133
+ def finalize_session(session_id: str, status: str = "ended") -> bool:
134
+ """
135
+ Finalize session (called by SessionEnd hook).
136
+
137
+ Archives the session and updates last activity timestamp.
138
+
139
+ Args:
140
+ session_id: Session ID to finalize
141
+ status: Final status (ended, failed, etc.)
142
+
143
+ Returns:
144
+ True if finalization succeeded, False otherwise
145
+ """
146
+ from htmlgraph.session_registry import SessionRegistry
147
+
148
+ try:
149
+ registry = SessionRegistry()
150
+ instance_id = registry.get_instance_id()
151
+
152
+ # Archive the session
153
+ success = registry.archive_session(instance_id)
154
+ if success:
155
+ logger.info(f"Archived session {session_id} (status: {status})")
156
+ else:
157
+ logger.warning(f"Failed to archive session {session_id}")
158
+
159
+ return success
160
+ except Exception as e:
161
+ logger.error(f"Failed to finalize session {session_id}: {e}")
162
+ return False
163
+
164
+
165
+ def heartbeat(session_id: str | None = None) -> bool:
166
+ """
167
+ Update session activity timestamp (liveness heartbeat).
168
+
169
+ Called periodically to indicate the session is still active.
170
+ Should be called on each tool use or periodically (e.g., every 5 minutes).
171
+
172
+ Args:
173
+ session_id: Optional session ID (uses current instance if None)
174
+
175
+ Returns:
176
+ True if heartbeat succeeded, False otherwise
177
+ """
178
+ from htmlgraph.session_registry import SessionRegistry
179
+
180
+ try:
181
+ registry = SessionRegistry()
182
+ instance_id = registry.get_instance_id()
183
+
184
+ success = registry.update_activity(instance_id)
185
+ if success:
186
+ logger.debug(f"Updated activity for instance {instance_id}")
187
+ else:
188
+ logger.warning(f"Failed to update activity for instance {instance_id}")
189
+
190
+ return success
191
+ except Exception as e:
192
+ logger.error(f"Failed to update activity: {e}")
193
+ return False
194
+
195
+
196
+ def get_current_session() -> dict | None:
197
+ """
198
+ Get current session for this instance.
199
+
200
+ Returns the registration data for the current instance's session.
201
+
202
+ Returns:
203
+ Session dict with instance_id, session_id, repo, etc., or None if not found
204
+ """
205
+ from htmlgraph.session_registry import SessionRegistry
206
+
207
+ try:
208
+ registry = SessionRegistry()
209
+ instance_id = registry.get_instance_id()
210
+ session = registry.read_session(instance_id)
211
+ return session
212
+ except Exception as e:
213
+ logger.error(f"Failed to get current session: {e}")
214
+ return None
215
+
216
+
217
+ def get_parent_session_id() -> str | None:
218
+ """
219
+ Get parent session ID if this is a spawned task.
220
+
221
+ Returns:
222
+ Parent session ID from environment, or None if not a spawned task
223
+ """
224
+ return _get_parent_session_id()
225
+
226
+
227
+ # Private helpers
228
+
229
+
230
+ def _get_hostname() -> str:
231
+ """Get hostname safely."""
232
+ try:
233
+ import socket
234
+
235
+ return socket.gethostname()
236
+ except Exception:
237
+ return "unknown"
238
+
239
+
240
+ def _get_utc_timestamp() -> str:
241
+ """Get current UTC timestamp in ISO 8601 format."""
242
+ now = datetime.now(timezone.utc)
243
+ return now.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
244
+
245
+
246
+ def _export_to_env_file(
247
+ env_file: str,
248
+ session_id: str,
249
+ instance_id: str,
250
+ repo_hash: str,
251
+ ) -> None:
252
+ """
253
+ Export session environment variables to CLAUDE_ENV_FILE.
254
+
255
+ Appends to the file to preserve any existing variables.
256
+
257
+ Args:
258
+ env_file: Path to environment file
259
+ session_id: Session ID to export
260
+ instance_id: Instance ID to export
261
+ repo_hash: Repository hash to export
262
+
263
+ Raises:
264
+ OSError: If file write fails
265
+ """
266
+ env_path = Path(env_file)
267
+
268
+ try:
269
+ # Append to environment file
270
+ with open(env_path, "a") as f:
271
+ f.write(f"export HTMLGRAPH_SESSION_ID={session_id}\n")
272
+ f.write(f"export HTMLGRAPH_INSTANCE_ID={instance_id}\n")
273
+ f.write(f"export HTMLGRAPH_REPO_HASH={repo_hash}\n")
274
+
275
+ logger.debug(f"Exported environment variables to {env_file}")
276
+ except OSError as e:
277
+ logger.error(f"Failed to export environment to {env_file}: {e}")
278
+ raise
279
+
280
+
281
+ def _get_parent_session_id() -> str | None:
282
+ """
283
+ Detect parent session from environment.
284
+
285
+ Checks:
286
+ 1. HTMLGRAPH_PARENT_SESSION_ID env var (set by Task spawning)
287
+ 2. HTMLGRAPH_PARENT_SESSION env var (alternate name)
288
+
289
+ Returns:
290
+ Parent session ID if found, None otherwise
291
+ """
292
+ parent_id = os.environ.get("HTMLGRAPH_PARENT_SESSION_ID")
293
+ if parent_id:
294
+ return parent_id
295
+
296
+ parent_id = os.environ.get("HTMLGRAPH_PARENT_SESSION")
297
+ if parent_id:
298
+ return parent_id
299
+
300
+ return None