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,436 @@
1
+ """
2
+ SessionStateManager - Automatic session state and environment variable management.
3
+
4
+ Provides automatic detection and management of:
5
+ - Current session state (ID, source, compaction status)
6
+ - Environment variable setup (CLAUDE_SESSION_ID, CLAUDE_SESSION_SOURCE, etc.)
7
+ - Delegation status determination
8
+ - Session metadata recording
9
+
10
+ Integration with SessionStart hook:
11
+ from htmlgraph import SDK
12
+
13
+ sdk = SDK()
14
+ state = sdk.sessions.get_current_state()
15
+ sdk.sessions.setup_environment_variables(state)
16
+
17
+ # All environment variables now set automatically
18
+ # Delegation status available via state['delegation_enabled']
19
+ """
20
+
21
+ import json
22
+ import logging
23
+ import os
24
+ import subprocess
25
+ from datetime import datetime, timezone
26
+ from pathlib import Path
27
+ from typing import Any, TypedDict
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class SessionState(TypedDict, total=False):
33
+ """Current session state information."""
34
+
35
+ session_id: str
36
+ session_source: str # "startup", "resume", "compact", "clear"
37
+ is_post_compact: bool
38
+ previous_session_id: str | None
39
+ delegation_enabled: bool
40
+ prompt_injected: bool
41
+ session_valid: bool
42
+ timestamp: str
43
+ compact_metadata: dict[str, Any]
44
+
45
+
46
+ class SessionStateManager:
47
+ """
48
+ Manages session state detection and environment variable setup.
49
+
50
+ Automatically detects:
51
+ - Current session ID and source
52
+ - Post-compact state
53
+ - Delegation status
54
+ - Session validity
55
+
56
+ Provides environment variable management:
57
+ - CLAUDE_SESSION_ID
58
+ - CLAUDE_SESSION_SOURCE
59
+ - CLAUDE_SESSION_COMPACTED
60
+ - CLAUDE_DELEGATION_ENABLED
61
+ - CLAUDE_ORCHESTRATOR_ACTIVE
62
+ - CLAUDE_PROMPT_PERSISTENCE_VERSION
63
+ """
64
+
65
+ # Session state metadata file
66
+ CURRENT_SESSION_FILE = "current.json"
67
+ SESSION_STATE_FILE = "session_state.json"
68
+ COMPACT_MARKER_FILE = ".compacted"
69
+
70
+ def __init__(self, graph_dir: str | Path):
71
+ """
72
+ Initialize SessionStateManager.
73
+
74
+ Args:
75
+ graph_dir: Path to .htmlgraph directory
76
+ """
77
+ self.graph_dir = Path(graph_dir)
78
+ self.sessions_dir = self.graph_dir / "sessions"
79
+ self.sessions_dir.mkdir(parents=True, exist_ok=True)
80
+
81
+ def get_current_state(self) -> SessionState:
82
+ """
83
+ Get current session state with automatic detection.
84
+
85
+ Returns:
86
+ SessionState dict with:
87
+ - session_id: Current session identifier
88
+ - session_source: "startup", "resume", "compact", "clear"
89
+ - is_post_compact: True if this is post-compact
90
+ - previous_session_id: Previous session ID if available
91
+ - delegation_enabled: Should delegation be active
92
+ - prompt_injected: Was orchestrator prompt injected
93
+ - session_valid: Is session valid for work
94
+ - timestamp: Current UTC timestamp
95
+ - compact_metadata: Compact detection details
96
+ """
97
+ # Get current session ID from environment or generate
98
+ session_id = os.environ.get("CLAUDE_SESSION_ID")
99
+ if not session_id:
100
+ session_id = self._generate_session_id()
101
+
102
+ # Load previous state
103
+ prev_state = self._load_previous_state()
104
+
105
+ # Detect session source and compaction
106
+ source, is_post_compact, compact_metadata = self._detect_session_source(
107
+ session_id, prev_state
108
+ )
109
+
110
+ # Determine delegation status
111
+ delegation_enabled = self._should_enable_delegation(is_post_compact, prev_state)
112
+
113
+ # Check if session is valid
114
+ session_valid = self._is_session_valid(session_id, prev_state)
115
+
116
+ # Get previous session ID
117
+ previous_session_id = prev_state.get("session_id") if prev_state else None
118
+
119
+ # Check if orchestrator prompt was injected
120
+ prompt_injected = os.environ.get("CLAUDE_ORCHESTRATOR_ACTIVE") == "true"
121
+
122
+ state: SessionState = {
123
+ "session_id": session_id,
124
+ "session_source": source,
125
+ "is_post_compact": is_post_compact,
126
+ "previous_session_id": previous_session_id,
127
+ "delegation_enabled": delegation_enabled,
128
+ "prompt_injected": prompt_injected,
129
+ "session_valid": session_valid,
130
+ "timestamp": datetime.now(timezone.utc).isoformat(),
131
+ "compact_metadata": compact_metadata,
132
+ }
133
+
134
+ return state
135
+
136
+ def setup_environment_variables(
137
+ self,
138
+ session_state: SessionState | None = None,
139
+ auto_detect_compact: bool = True,
140
+ ) -> dict[str, str]:
141
+ """
142
+ Automatically set up environment variables for session state.
143
+
144
+ Args:
145
+ session_state: Session state dict (auto-detected if not provided)
146
+ auto_detect_compact: Whether to auto-detect post-compact state
147
+
148
+ Returns:
149
+ Dict of environment variables that were set
150
+
151
+ Sets the following environment variables:
152
+ - CLAUDE_SESSION_ID: Current session identifier
153
+ - CLAUDE_SESSION_SOURCE: "startup|resume|compact|clear"
154
+ - CLAUDE_SESSION_COMPACTED: "true|false"
155
+ - CLAUDE_DELEGATION_ENABLED: "true|false"
156
+ - CLAUDE_PREVIOUS_SESSION_ID: Previous session ID
157
+ - CLAUDE_ORCHESTRATOR_ACTIVE: "true|false"
158
+ - CLAUDE_PROMPT_PERSISTENCE_VERSION: "1.0"
159
+ """
160
+ if session_state is None:
161
+ session_state = self.get_current_state()
162
+
163
+ env_vars = {}
164
+
165
+ # CLAUDE_SESSION_ID - always set
166
+ session_id = session_state.get("session_id", "unknown")
167
+ os.environ["CLAUDE_SESSION_ID"] = session_id
168
+ env_vars["CLAUDE_SESSION_ID"] = session_id
169
+
170
+ # CLAUDE_SESSION_SOURCE - session type detection
171
+ source = session_state.get("session_source", "startup")
172
+ os.environ["CLAUDE_SESSION_SOURCE"] = source
173
+ env_vars["CLAUDE_SESSION_SOURCE"] = source
174
+
175
+ # CLAUDE_SESSION_COMPACTED - post-compact detection
176
+ is_post_compact = session_state.get("is_post_compact", False)
177
+ os.environ["CLAUDE_SESSION_COMPACTED"] = str(is_post_compact).lower()
178
+ env_vars["CLAUDE_SESSION_COMPACTED"] = str(is_post_compact).lower()
179
+
180
+ # CLAUDE_DELEGATION_ENABLED - orchestrator delegation
181
+ delegation_enabled = session_state.get("delegation_enabled", False)
182
+ os.environ["CLAUDE_DELEGATION_ENABLED"] = str(delegation_enabled).lower()
183
+ env_vars["CLAUDE_DELEGATION_ENABLED"] = str(delegation_enabled).lower()
184
+
185
+ # CLAUDE_PREVIOUS_SESSION_ID - for tracking continuity
186
+ prev_session = session_state.get("previous_session_id")
187
+ if prev_session:
188
+ os.environ["CLAUDE_PREVIOUS_SESSION_ID"] = prev_session
189
+ env_vars["CLAUDE_PREVIOUS_SESSION_ID"] = prev_session
190
+
191
+ # CLAUDE_ORCHESTRATOR_ACTIVE - skill activation
192
+ orchestrator_active = session_state.get("delegation_enabled", False)
193
+ os.environ["CLAUDE_ORCHESTRATOR_ACTIVE"] = str(orchestrator_active).lower()
194
+ env_vars["CLAUDE_ORCHESTRATOR_ACTIVE"] = str(orchestrator_active).lower()
195
+
196
+ # CLAUDE_PROMPT_PERSISTENCE_VERSION - version management
197
+ os.environ["CLAUDE_PROMPT_PERSISTENCE_VERSION"] = "1.0"
198
+ env_vars["CLAUDE_PROMPT_PERSISTENCE_VERSION"] = "1.0"
199
+
200
+ # Record state metadata
201
+ self.record_state(
202
+ session_id=session_id,
203
+ source=source,
204
+ is_post_compact=is_post_compact,
205
+ delegation_enabled=delegation_enabled,
206
+ environment_vars=env_vars,
207
+ )
208
+
209
+ return env_vars
210
+
211
+ def record_state(
212
+ self,
213
+ session_id: str,
214
+ source: str,
215
+ is_post_compact: bool,
216
+ delegation_enabled: bool,
217
+ environment_vars: dict[str, str] | None = None,
218
+ ) -> None:
219
+ """
220
+ Store session state metadata for future reference.
221
+
222
+ Args:
223
+ session_id: Current session ID
224
+ source: Session source ("startup", "resume", "compact", "clear")
225
+ is_post_compact: Whether this is post-compact
226
+ delegation_enabled: Whether delegation is enabled
227
+ environment_vars: Environment variables that were set
228
+ """
229
+ state_file = self.sessions_dir / self.SESSION_STATE_FILE
230
+
231
+ state_data = {
232
+ "session_id": session_id,
233
+ "source": source,
234
+ "is_post_compact": is_post_compact,
235
+ "delegation_enabled": delegation_enabled,
236
+ "timestamp": datetime.now(timezone.utc).isoformat(),
237
+ "environment_vars": environment_vars or {},
238
+ }
239
+
240
+ try:
241
+ state_file.write_text(json.dumps(state_data, indent=2))
242
+ except Exception as e:
243
+ logger.warning(f"Could not record session state: {e}")
244
+
245
+ def detect_compact_automatically(self) -> bool:
246
+ """
247
+ Auto-detect if this is post-compact by comparing session IDs.
248
+
249
+ Returns:
250
+ True if this is a post-compact session
251
+ """
252
+ current_id = os.environ.get("CLAUDE_SESSION_ID")
253
+ prev_state = self._load_previous_state()
254
+
255
+ if not current_id or not prev_state:
256
+ return False
257
+
258
+ prev_id = prev_state.get("session_id")
259
+ if current_id == prev_id:
260
+ return False # Same session, not post-compact
261
+
262
+ # Check if previous session was ended gracefully (SessionEnd hook called)
263
+ # This is a heuristic: if previous session exists and is marked as "ended",
264
+ # then this new session is likely post-compact
265
+ prev_session_ended = bool(prev_state.get("is_ended", False))
266
+ return prev_session_ended
267
+
268
+ # ========================================================================
269
+ # Private Methods
270
+ # ========================================================================
271
+
272
+ def _generate_session_id(self) -> str:
273
+ """Generate a stable session ID."""
274
+ from htmlgraph.ids import generate_id
275
+
276
+ return generate_id("session", "auto")
277
+
278
+ def _load_previous_state(self) -> dict[str, Any] | None:
279
+ """Load previous session state from file."""
280
+ state_file = self.sessions_dir / self.SESSION_STATE_FILE
281
+
282
+ if not state_file.exists():
283
+ return None
284
+
285
+ try:
286
+ data = json.loads(state_file.read_text())
287
+ assert isinstance(data, dict)
288
+ return data
289
+ except Exception as e:
290
+ logger.debug(f"Could not load previous state: {e}")
291
+ return None
292
+
293
+ def _detect_session_source(
294
+ self, current_id: str, prev_state: dict[str, Any] | None
295
+ ) -> tuple[str, bool, dict[str, Any]]:
296
+ """
297
+ Detect session source and compaction status.
298
+
299
+ Returns:
300
+ (source, is_post_compact, metadata)
301
+ where source is one of: "startup", "resume", "compact", "clear"
302
+ """
303
+ metadata: dict[str, Any] = {}
304
+
305
+ # No previous state = startup or fresh session
306
+ if not prev_state:
307
+ return "startup", False, metadata
308
+
309
+ prev_id = prev_state.get("session_id")
310
+
311
+ # Same session ID = resume (context switch within same Claude session)
312
+ if current_id == prev_id:
313
+ metadata["reason"] = "same_session_id"
314
+ return "resume", False, metadata
315
+
316
+ # Different session ID - check if previous was ended
317
+ prev_was_ended = prev_state.get("is_ended", False)
318
+
319
+ # Check for compact marker file
320
+ compact_marker = self.sessions_dir / self.COMPACT_MARKER_FILE
321
+ has_compact_marker = compact_marker.exists()
322
+
323
+ if has_compact_marker or prev_was_ended:
324
+ # This is post-compact
325
+ metadata["reason"] = "previous_session_ended"
326
+ metadata["previous_session_id"] = prev_id
327
+ metadata["had_compact_marker"] = has_compact_marker
328
+ return "compact", True, metadata
329
+
330
+ # Check if this looks like a /clear command
331
+ if self._detect_clear_command():
332
+ metadata["reason"] = "clear_command_detected"
333
+ return "clear", False, metadata
334
+
335
+ # Different session ID but previous wasn't ended - likely resume after context switch
336
+ metadata["reason"] = "different_session_id_no_end"
337
+ return "resume", False, metadata
338
+
339
+ def _should_enable_delegation(
340
+ self, is_post_compact: bool, prev_state: dict[str, Any] | None
341
+ ) -> bool:
342
+ """
343
+ Determine if delegation should be enabled.
344
+
345
+ Returns:
346
+ True if delegation should be active in this session
347
+ """
348
+ # Check environment variable override
349
+ if os.environ.get("HTMLGRAPH_DELEGATION_DISABLE") == "1":
350
+ return False
351
+
352
+ # Enable delegation if:
353
+ # 1. This is post-compact (context carry-over needed)
354
+ # 2. Previous session had delegation enabled
355
+ # 3. Features are available to work on
356
+ if is_post_compact:
357
+ return True
358
+
359
+ if prev_state:
360
+ if prev_state.get("delegation_enabled", False):
361
+ return True
362
+
363
+ # Check if there are features available
364
+ if self._has_available_work():
365
+ return True
366
+
367
+ return False
368
+
369
+ def _is_session_valid(
370
+ self, session_id: str, prev_state: dict[str, Any] | None
371
+ ) -> bool:
372
+ """
373
+ Check if session is valid for work tracking.
374
+
375
+ Returns:
376
+ True if this is a valid session for work
377
+ """
378
+ # Check if session directory exists
379
+ sessions_dir = self.graph_dir / "sessions"
380
+ if not sessions_dir.exists():
381
+ return True # Valid - directory will be created
382
+
383
+ # Check if we can write to sessions directory
384
+ try:
385
+ test_file = sessions_dir / ".test"
386
+ test_file.touch()
387
+ test_file.unlink()
388
+ return True
389
+ except Exception:
390
+ logger.warning("Cannot write to sessions directory")
391
+ return False
392
+
393
+ def _detect_clear_command(self) -> bool:
394
+ """
395
+ Detect if /clear command was run (clears .htmlgraph state).
396
+
397
+ Returns:
398
+ True if /clear was likely run
399
+ """
400
+ # Check if clear marker exists
401
+ clear_marker = self.graph_dir / ".clear_marker"
402
+ return clear_marker.exists()
403
+
404
+ def _has_available_work(self) -> bool:
405
+ """
406
+ Check if there are features available to work on.
407
+
408
+ Returns:
409
+ True if there are features in todo or in-progress state
410
+ """
411
+ features_dir = self.graph_dir / "features"
412
+ if not features_dir.exists():
413
+ return False
414
+
415
+ try:
416
+ html_files = list(features_dir.glob("*.html"))
417
+ return len(html_files) > 0
418
+ except Exception:
419
+ return False
420
+
421
+ @staticmethod
422
+ def _get_git_status() -> dict[str, Any]:
423
+ """Get current git status (for compaction detection)."""
424
+ try:
425
+ result = subprocess.run(
426
+ ["git", "status", "--porcelain"],
427
+ capture_output=True,
428
+ text=True,
429
+ timeout=5,
430
+ )
431
+ return {
432
+ "has_changes": bool(result.stdout.strip()),
433
+ "status_output": result.stdout.strip(),
434
+ }
435
+ except Exception:
436
+ return {"has_changes": False, "status_output": ""}
@@ -0,0 +1,201 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Session Warning System for AI Agents.
5
+
6
+ Provides a mechanism to show critical instructions to AI agents at session start,
7
+ working around Claude Code's SessionStart hook bug (#10373) where additionalContext
8
+ is not injected for new conversations.
9
+
10
+ The warning:
11
+ 1. Shows on every new session (first SDK usage)
12
+ 2. Contains orchestrator instructions
13
+ 3. Requires explicit dismissal (confirming agent read it)
14
+
15
+ Usage:
16
+ from htmlgraph import SDK
17
+
18
+ sdk = SDK(agent="claude")
19
+ # Warning automatically shown if not dismissed
20
+
21
+ # Agent dismisses after reading (as first action)
22
+ sdk.dismiss_session_warning()
23
+ """
24
+
25
+
26
+ import json
27
+ import sys
28
+ from datetime import datetime
29
+ from pathlib import Path
30
+ from typing import Any
31
+
32
+ # The orchestrator instructions that agents MUST see
33
+ ORCHESTRATOR_WARNING = """
34
+ ╔══════════════════════════════════════════════════════════════════════════════╗
35
+ ║ HTMLGRAPH ORCHESTRATOR MODE ║
36
+ ╠══════════════════════════════════════════════════════════════════════════════╣
37
+ ║ ║
38
+ ║ YOU ARE THE ORCHESTRATOR. Follow these directives: ║
39
+ ║ ║
40
+ ║ 1. DELEGATE exploration → Task(subagent_type="Explore") ║
41
+ ║ 2. DELEGATE implementation → sdk.spawn_coder(feature_id, context) ║
42
+ ║ 3. CREATE work items BEFORE code changes → sdk.features.create().save() ║
43
+ ║ 4. PARALLELIZE independent tasks → Multiple Task() calls in ONE message ║
44
+ ║ 5. USE SDK METHODS not raw prompts → sdk.orchestrate(), sdk.spawn_coder() ║
45
+ ║ ║
46
+ ║ SDK QUICK REFERENCE: ║
47
+ ║ sdk.spawn_coder(feature_id, context) # Generate coder prompt ║
48
+ ║ sdk.spawn_explorer(task, scope) # Generate explorer prompt ║
49
+ ║ sdk.orchestrate(feature_id, scope) # Full orchestration workflow ║
50
+ ║ sdk.plan_parallel_work(max_agents) # Get parallelizable work ║
51
+ ║ ║
52
+ ║ ANTI-PATTERNS TO AVOID: ║
53
+ ║ ❌ Raw Task prompts without sdk.spawn_coder() ║
54
+ ║ ❌ Sequential Task calls (use ONE message for parallelism) ║
55
+ ║ ❌ Code changes without creating a feature first ║
56
+ ║ ❌ Manual file edits on .htmlgraph/ (use SDK instead) ║
57
+ ║ ║
58
+ ║ FIRST ACTION: Dismiss this warning to confirm you've read it: ║
59
+ ║ >>> sdk.dismiss_session_warning() ║
60
+ ║ ║
61
+ ╚══════════════════════════════════════════════════════════════════════════════╝
62
+ """
63
+
64
+
65
+ class SessionWarning:
66
+ """
67
+ Manages session warning state for AI agents.
68
+
69
+ Shows critical orchestrator instructions on first SDK usage,
70
+ requires explicit dismissal to confirm agent read them.
71
+ """
72
+
73
+ WARNING_FILE = ".session-warning-state.json"
74
+
75
+ def __init__(self, graph_dir: Path):
76
+ self.graph_dir = Path(graph_dir)
77
+ self.state_file = self.graph_dir / self.WARNING_FILE
78
+ self._state: dict[str, Any] = self._load_state()
79
+
80
+ def _load_state(self) -> dict[str, Any]:
81
+ """Load warning state from file."""
82
+ if self.state_file.exists():
83
+ try:
84
+ data = json.loads(self.state_file.read_text())
85
+ if isinstance(data, dict):
86
+ return data
87
+ except (json.JSONDecodeError, OSError):
88
+ pass
89
+ return {
90
+ "dismissed_at": None,
91
+ "dismissed_by": None,
92
+ "session_id": None,
93
+ "show_count": 0,
94
+ }
95
+
96
+ def _save_state(self) -> None:
97
+ """Save warning state to file."""
98
+ try:
99
+ self.state_file.write_text(json.dumps(self._state, indent=2))
100
+ except OSError:
101
+ pass
102
+
103
+ def should_show(self, session_id: str | None = None) -> bool:
104
+ """
105
+ Check if warning should be shown.
106
+
107
+ Shows warning if:
108
+ - Never dismissed, OR
109
+ - Different session than last dismissal
110
+ """
111
+ # Always show if never dismissed
112
+ if not self._state.get("dismissed_at"):
113
+ return True
114
+
115
+ # Show if different session
116
+ if session_id and self._state.get("session_id") != session_id:
117
+ return True
118
+
119
+ return False
120
+
121
+ def show(self, agent: str | None = None, session_id: str | None = None) -> None:
122
+ """
123
+ Show the warning to stderr (visible to agent).
124
+
125
+ Args:
126
+ agent: Agent identifier
127
+ session_id: Current session ID
128
+ """
129
+ self._state["show_count"] = self._state.get("show_count", 0) + 1
130
+ self._save_state()
131
+
132
+ # Print to stderr so it's visible to the agent
133
+ print(ORCHESTRATOR_WARNING, file=sys.stderr)
134
+
135
+ # Also print dismissal reminder
136
+ print(
137
+ f"\n⚠️ WARNING: Orchestrator instructions shown ({self._state['show_count']} times). "
138
+ f"Dismiss with: sdk.dismiss_session_warning()\n",
139
+ file=sys.stderr,
140
+ )
141
+
142
+ def dismiss(self, agent: str | None = None, session_id: str | None = None) -> bool:
143
+ """
144
+ Dismiss the warning for this session.
145
+
146
+ Args:
147
+ agent: Agent that dismissed
148
+ session_id: Current session ID
149
+
150
+ Returns:
151
+ True if dismissed, False if already dismissed for this session
152
+ """
153
+ was_new = self.should_show(session_id)
154
+
155
+ self._state["dismissed_at"] = datetime.now().isoformat()
156
+ self._state["dismissed_by"] = agent
157
+ self._state["session_id"] = session_id
158
+ self._save_state()
159
+
160
+ if was_new:
161
+ print(
162
+ "✅ Orchestrator warning dismissed. You may now proceed with delegating work.",
163
+ file=sys.stderr,
164
+ )
165
+
166
+ return was_new
167
+
168
+ def get_status(self) -> dict[str, Any]:
169
+ """Get current warning status."""
170
+ return {
171
+ "dismissed": bool(self._state.get("dismissed_at")),
172
+ "dismissed_at": self._state.get("dismissed_at"),
173
+ "dismissed_by": self._state.get("dismissed_by"),
174
+ "show_count": self._state.get("show_count", 0),
175
+ }
176
+
177
+
178
+ def check_and_show_warning(
179
+ graph_dir: Path,
180
+ agent: str | None = None,
181
+ session_id: str | None = None,
182
+ ) -> SessionWarning:
183
+ """
184
+ Check if warning should be shown and show it if needed.
185
+
186
+ Called automatically from SDK.__init__.
187
+
188
+ Args:
189
+ graph_dir: Path to .htmlgraph directory
190
+ agent: Agent identifier
191
+ session_id: Current session ID
192
+
193
+ Returns:
194
+ SessionWarning instance for dismissal
195
+ """
196
+ warning = SessionWarning(graph_dir)
197
+
198
+ if warning.should_show(session_id):
199
+ warning.show(agent=agent, session_id=session_id)
200
+
201
+ return warning
@@ -0,0 +1,23 @@
1
+ """
2
+ Session management and continuity.
3
+
4
+ Provides session lifecycle, handoff, and resumption features.
5
+ """
6
+
7
+ from htmlgraph.sessions.handoff import (
8
+ ContextRecommender,
9
+ HandoffBuilder,
10
+ HandoffMetrics,
11
+ HandoffTracker,
12
+ SessionResume,
13
+ SessionResumeInfo,
14
+ )
15
+
16
+ __all__ = [
17
+ "HandoffBuilder",
18
+ "SessionResume",
19
+ "SessionResumeInfo",
20
+ "HandoffTracker",
21
+ "HandoffMetrics",
22
+ "ContextRecommender",
23
+ ]