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
@@ -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
@@ -27,7 +27,7 @@ from htmlgraph.event_log import EventRecord, JsonlEventLog
27
27
  from htmlgraph.exceptions import SessionNotFoundError
28
28
  from htmlgraph.graph import HtmlGraph
29
29
  from htmlgraph.ids import generate_id
30
- from htmlgraph.models import ActivityEntry, Node, Session
30
+ from htmlgraph.models import ActivityEntry, ErrorEntry, Node, Session
31
31
  from htmlgraph.services import ClaimingService
32
32
  from htmlgraph.spike_index import ActiveAutoSpikeIndex
33
33
 
@@ -186,7 +186,7 @@ class SessionManager:
186
186
  """Mark a session as stale (kept for history but not considered active)."""
187
187
  if session.status != "active":
188
188
  return
189
- now = datetime.now()
189
+ now = datetime.now(timezone.utc)
190
190
  session.status = "stale"
191
191
  session.ended_at = now
192
192
  session.last_activity = now
@@ -230,6 +230,7 @@ class SessionManager:
230
230
  continued_from: str | None = None,
231
231
  start_commit: str | None = None,
232
232
  title: str | None = None,
233
+ parent_session_id: str | None = None,
233
234
  ) -> Session:
234
235
  """
235
236
  Start a new session.
@@ -241,6 +242,7 @@ class SessionManager:
241
242
  continued_from: Previous session ID if continuing
242
243
  start_commit: Git commit hash at session start
243
244
  title: Optional human-readable title
245
+ parent_session_id: ID of parent session (for subagents)
244
246
 
245
247
  Returns:
246
248
  New Session instance
@@ -309,6 +311,7 @@ class SessionManager:
309
311
  started_at=now,
310
312
  last_activity=now,
311
313
  title=title or "",
314
+ parent_session=parent_session_id,
312
315
  )
313
316
 
314
317
  # Add session start event
@@ -320,6 +323,12 @@ class SessionManager:
320
323
  )
321
324
  )
322
325
 
326
+ # Set parent session in environment for subsequent subprocesses (e.g. HeadlessSpawner)
327
+ # This ensures that any tools spawned by this session link back to it
328
+ import os
329
+
330
+ os.environ["HTMLGRAPH_PARENT_SESSION"] = session.id
331
+
323
332
  # Save to disk
324
333
  self.session_converter.save(session)
325
334
  self._sessions_cache_dirty = True
@@ -733,7 +742,7 @@ class SessionManager:
733
742
  ActivityEntry(
734
743
  tool="SessionEnd",
735
744
  summary="Session ended",
736
- timestamp=datetime.now(),
745
+ timestamp=datetime.now(timezone.utc),
737
746
  )
738
747
  )
739
748
 
@@ -783,6 +792,158 @@ class SessionManager:
783
792
 
784
793
  return session
785
794
 
795
+ def continue_from_last(
796
+ self,
797
+ agent: str | None = None,
798
+ auto_create_session: bool = True,
799
+ ) -> tuple[Session | None, Any]: # Returns (new_session, resume_info)
800
+ """
801
+ Continue work from the last completed session.
802
+
803
+ Loads context from the previous session including:
804
+ - Handoff notes and next focus
805
+ - Blockers
806
+ - Recommended context files
807
+ - Recent commits
808
+ - Features worked on
809
+
810
+ Args:
811
+ agent: Filter by agent (None = current agent)
812
+ auto_create_session: Create new session if True
813
+
814
+ Returns:
815
+ Tuple of (new_session, resume_info) or (None, None) if no previous session
816
+
817
+ Example:
818
+ >>> manager = SessionManager(".htmlgraph")
819
+ >>> new_session, resume = manager.continue_from_last(agent="claude")
820
+ >>> if resume:
821
+ ... print(resume.summary)
822
+ ... print(resume.recommended_files)
823
+ """
824
+ # Import handoff module
825
+ from typing import Any
826
+
827
+ from htmlgraph.sessions.handoff import SessionResume
828
+
829
+ # Create a minimal SDK-like object with just the directory
830
+ # to avoid circular dependency and database initialization issues
831
+ class MinimalSDK:
832
+ def __init__(self, directory: Path) -> None:
833
+ self._directory = directory
834
+
835
+ sdk: Any = MinimalSDK(self.graph_dir)
836
+ resume = SessionResume(sdk)
837
+
838
+ # Get last session
839
+ last_session = resume.get_last_session(agent=agent)
840
+ if not last_session:
841
+ return None, None
842
+
843
+ # Build resume info
844
+ resume_info = resume.build_resume_info(last_session)
845
+
846
+ # Create new session if requested
847
+ new_session = None
848
+ if auto_create_session:
849
+ from htmlgraph.ids import generate_id
850
+
851
+ session_id = generate_id("sess")
852
+ new_session = self.start_session(
853
+ session_id=session_id,
854
+ agent=agent or last_session.agent,
855
+ title=f"Continuing from {last_session.id}",
856
+ )
857
+
858
+ # Link to previous session
859
+ new_session.continued_from = last_session.id
860
+ self.session_converter.save(new_session)
861
+
862
+ return new_session, resume_info
863
+
864
+ def end_session_with_handoff(
865
+ self,
866
+ session_id: str,
867
+ summary: str | None = None,
868
+ next_focus: str | None = None,
869
+ blockers: list[str] | None = None,
870
+ keep_context: list[str] | None = None,
871
+ auto_recommend_context: bool = True,
872
+ ) -> Session | None:
873
+ """
874
+ End session with handoff information for next session.
875
+
876
+ Args:
877
+ session_id: Session to end
878
+ summary: What was accomplished (handoff notes)
879
+ next_focus: What should be done next
880
+ blockers: List of blockers preventing progress
881
+ keep_context: List of files to keep context for
882
+ auto_recommend_context: Auto-recommend files from git history
883
+
884
+ Returns:
885
+ Updated session or None
886
+
887
+ Example:
888
+ >>> manager.end_session_with_handoff(
889
+ ... session_id="sess-123",
890
+ ... summary="Completed OAuth integration",
891
+ ... next_focus="Implement JWT token refresh",
892
+ ... blockers=["Waiting for security review"],
893
+ ... keep_context=["src/auth/oauth.py"]
894
+ ... )
895
+ """
896
+ from htmlgraph.sessions.handoff import (
897
+ ContextRecommender,
898
+ HandoffBuilder,
899
+ )
900
+
901
+ # Get session
902
+ session = self.get_session(session_id)
903
+ if not session:
904
+ return None
905
+
906
+ # Build handoff using HandoffBuilder
907
+ builder = HandoffBuilder(session)
908
+
909
+ if summary:
910
+ builder.add_summary(summary)
911
+
912
+ if next_focus:
913
+ builder.add_next_focus(next_focus)
914
+
915
+ if blockers:
916
+ builder.add_blockers(blockers)
917
+
918
+ if keep_context:
919
+ builder.add_context_files(keep_context)
920
+
921
+ # Auto-recommend context files
922
+ if auto_recommend_context:
923
+ recommender = ContextRecommender()
924
+ builder.auto_recommend_context(recommender, max_files=10)
925
+
926
+ handoff_data = builder.build()
927
+
928
+ # Update session with handoff data
929
+ session.handoff_notes = handoff_data["handoff_notes"]
930
+ session.recommended_next = handoff_data["recommended_next"]
931
+ session.blockers = handoff_data["blockers"]
932
+ session.recommended_context = handoff_data["recommended_context"]
933
+
934
+ # Persist handoff data to database before ending session
935
+ self.session_converter.save(session)
936
+
937
+ # End the session
938
+ self.end_session(session_id)
939
+
940
+ # Track handoff effectiveness (optional - only if database available)
941
+ # Note: SessionManager doesn't have direct database access,
942
+ # handoff tracking is primarily done through SDK
943
+ # Users should use SDK.end_session_with_handoff() for full tracking
944
+
945
+ return session
946
+
786
947
  def release_session_features(self, session_id: str) -> list[str]:
787
948
  """
788
949
  Release all features claimed by a specific session.
@@ -795,6 +956,126 @@ class SessionManager:
795
956
  """
796
957
  return self.claiming_service.release_session_features(session_id)
797
958
 
959
+ def log_error(
960
+ self,
961
+ session_id: str,
962
+ error: Exception,
963
+ traceback_str: str,
964
+ context: dict[str, Any] | None = None,
965
+ ) -> None:
966
+ """
967
+ Log error with full traceback to session.
968
+
969
+ Stores complete error details for later retrieval via debug command.
970
+ Minimizes console output for better token efficiency.
971
+
972
+ Args:
973
+ session_id: Session ID to log error to
974
+ error: The exception object
975
+ traceback_str: Full traceback string
976
+ context: Optional context dict (e.g. current file, line number)
977
+ """
978
+ session = self.get_session(session_id)
979
+ if not session:
980
+ return
981
+
982
+ error_entry = ErrorEntry(
983
+ timestamp=datetime.now(),
984
+ error_type=error.__class__.__name__,
985
+ message=str(error),
986
+ traceback=traceback_str,
987
+ )
988
+
989
+ # Append error record to error_log
990
+ session.error_log.append(error_entry)
991
+
992
+ # Save updated session
993
+ self.session_converter.save(session)
994
+
995
+ def get_session_errors(self, session_id: str) -> list[dict[str, Any]]:
996
+ """
997
+ Retrieve all errors logged for a session.
998
+
999
+ Args:
1000
+ session_id: Session ID
1001
+
1002
+ Returns:
1003
+ List of error records, or empty list if none
1004
+ """
1005
+ session = self.get_session(session_id)
1006
+ if not session:
1007
+ return []
1008
+ return [error.model_dump() for error in session.error_log]
1009
+
1010
+ def search_errors(
1011
+ self,
1012
+ session_id: str,
1013
+ error_type: str | None = None,
1014
+ pattern: str | None = None,
1015
+ ) -> list[dict[str, Any]]:
1016
+ """
1017
+ Search errors in a session by type and/or pattern.
1018
+
1019
+ Args:
1020
+ session_id: Session ID to search
1021
+ error_type: Filter by exception type (e.g., "ValueError")
1022
+ pattern: Regex pattern to match in error message
1023
+
1024
+ Returns:
1025
+ List of matching error records
1026
+ """
1027
+ session = self.get_session(session_id)
1028
+ if not session:
1029
+ return []
1030
+
1031
+ errors = [error.model_dump() for error in session.error_log]
1032
+
1033
+ # Filter by error type
1034
+ if error_type:
1035
+ errors = [e for e in errors if e.get("error_type") == error_type]
1036
+
1037
+ # Filter by pattern in message
1038
+ if pattern:
1039
+ compiled_pattern = re.compile(pattern, re.IGNORECASE)
1040
+ errors = [
1041
+ e for e in errors if compiled_pattern.search(e.get("message", ""))
1042
+ ]
1043
+
1044
+ return errors
1045
+
1046
+ def get_error_summary(self, session_id: str) -> dict[str, Any]:
1047
+ """
1048
+ Get summary statistics of errors in a session.
1049
+
1050
+ Args:
1051
+ session_id: Session ID
1052
+
1053
+ Returns:
1054
+ Dictionary with error summary statistics
1055
+ """
1056
+ session = self.get_session(session_id)
1057
+ if not session or not session.error_log:
1058
+ return {
1059
+ "total_errors": 0,
1060
+ "error_types": {},
1061
+ "first_error": None,
1062
+ "last_error": None,
1063
+ }
1064
+
1065
+ errors = session.error_log
1066
+ error_types: dict[str, int] = {}
1067
+
1068
+ for error in errors:
1069
+ error_type = error.error_type
1070
+ error_types[error_type] = error_types.get(error_type, 0) + 1
1071
+
1072
+ return {
1073
+ "total_errors": len(errors),
1074
+ "error_types": error_types,
1075
+ "first_error": errors[0].model_dump() if errors else None,
1076
+ "last_error": errors[-1].model_dump() if errors else None,
1077
+ }
1078
+
798
1079
  # =========================================================================
799
1080
  # Activity Tracking
800
1081
  # =========================================================================
@@ -925,6 +1206,7 @@ class SessionManager:
925
1206
  work_type=work_type,
926
1207
  session_status=session.status,
927
1208
  file_paths=file_paths,
1209
+ parent_session_id=session.parent_session,
928
1210
  payload=entry.payload
929
1211
  if isinstance(entry.payload, dict)
930
1212
  else payload,