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,450 @@
1
+ from __future__ import annotations
2
+
3
+ """System prompt management for HtmlGraph projects.
4
+
5
+ Provides a two-tier system:
6
+ 1. Plugin Default - Included with HtmlGraph plugin, available to all users
7
+ 2. Project Override - Optional, project-specific customization
8
+
9
+ Architecture:
10
+ - System prompts are injected via SessionStart hook's additionalContext
11
+ - Survives Claude Code compact/resume cycles
12
+ - SDK provides methods for creation, validation, and management
13
+ """
14
+
15
+
16
+ import logging
17
+ from pathlib import Path
18
+ from typing import TYPE_CHECKING
19
+
20
+ if TYPE_CHECKING:
21
+ pass
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class SystemPromptValidator:
27
+ """Validate system prompts against token budgets and quality criteria."""
28
+
29
+ @staticmethod
30
+ def count_tokens(text: str) -> int:
31
+ """
32
+ Estimate or count tokens in text.
33
+
34
+ Uses tiktoken if available (accurate), falls back to character-based
35
+ estimation if tiktoken not installed.
36
+
37
+ Args:
38
+ text: Text to count tokens for
39
+
40
+ Returns:
41
+ Estimated or exact token count
42
+ """
43
+ if not text:
44
+ return 0
45
+
46
+ try:
47
+ import tiktoken
48
+
49
+ encoding = tiktoken.encoding_for_model("gpt-4")
50
+ return len(encoding.encode(text))
51
+ except Exception:
52
+ # Fallback: rough estimation (1 token ≈ 4 characters)
53
+ # This is a conservative estimate used by Claude
54
+ return max(1, len(text) // 4)
55
+
56
+ @staticmethod
57
+ def validate(
58
+ text: str,
59
+ max_tokens: int = 1000,
60
+ min_tokens: int = 50,
61
+ ) -> dict:
62
+ """
63
+ Validate system prompt against token budget and quality criteria.
64
+
65
+ Args:
66
+ text: Prompt text to validate
67
+ max_tokens: Maximum allowed tokens (default: 1000)
68
+ min_tokens: Minimum expected tokens (default: 50)
69
+
70
+ Returns:
71
+ Validation result dictionary:
72
+ {
73
+ "is_valid": bool,
74
+ "tokens": int,
75
+ "warnings": List[str],
76
+ "message": str
77
+ }
78
+ """
79
+ tokens = SystemPromptValidator.count_tokens(text)
80
+ warnings = []
81
+
82
+ # Token budget validation
83
+ if tokens > max_tokens:
84
+ warnings.append(f"Prompt exceeds budget: {tokens} > {max_tokens} tokens")
85
+
86
+ if tokens < min_tokens:
87
+ warnings.append(
88
+ f"Prompt is very short ({tokens} tokens) - "
89
+ f"may not provide sufficient guidance (minimum: {min_tokens})"
90
+ )
91
+
92
+ # Content quality checks
93
+ if len(text) < 100:
94
+ warnings.append(
95
+ "Prompt is very brief - consider adding more detail for better guidance"
96
+ )
97
+
98
+ # Determine validity
99
+ is_valid = min_tokens <= tokens <= max_tokens
100
+
101
+ # Build message
102
+ if is_valid:
103
+ message = (
104
+ f"Valid prompt: {tokens} tokens (within {max_tokens} token budget)"
105
+ )
106
+ elif tokens > max_tokens:
107
+ message = f"Invalid: {tokens} tokens exceeds {max_tokens} token limit"
108
+ else:
109
+ message = (
110
+ f"Warning: {tokens} tokens below recommended minimum ({min_tokens}). "
111
+ f"Prompt may not provide sufficient guidance."
112
+ )
113
+
114
+ return {
115
+ "is_valid": is_valid,
116
+ "tokens": tokens,
117
+ "warnings": warnings,
118
+ "message": message,
119
+ }
120
+
121
+
122
+ class SystemPromptManager:
123
+ """Manage system prompts for a project.
124
+
125
+ Provides methods to:
126
+ - Load plugin default system prompt
127
+ - Load/create project-level overrides
128
+ - Validate prompt token counts
129
+ - Manage prompt lifecycle
130
+
131
+ Architecture:
132
+ - Plugin Default: Included with plugin, loaded via importlib.resources
133
+ - Project Override: Optional .claude/system-prompt.md file
134
+ - Strategy: Project override takes precedence over plugin default
135
+ """
136
+
137
+ def __init__(self, graph_dir: Path | str):
138
+ """
139
+ Initialize system prompt manager.
140
+
141
+ Args:
142
+ graph_dir: Path to .htmlgraph directory
143
+ """
144
+ self.graph_dir = Path(graph_dir)
145
+ self.project_dir = self.graph_dir.parent
146
+ self.claude_dir = self.project_dir / ".claude"
147
+
148
+ def get_default(self) -> str | None:
149
+ """
150
+ Get plugin default system prompt.
151
+
152
+ Tries multiple strategies:
153
+ 1. Load via importlib.resources (when installed via pip)
154
+ 2. Load via package file path (development mode)
155
+ 3. Return None if not found
156
+
157
+ Returns:
158
+ Default prompt text, or None if not found
159
+ """
160
+ # Strategy 1: importlib.resources (standard Python 3.7+)
161
+ try:
162
+ from importlib.resources import files
163
+
164
+ try:
165
+ # Try new package structure (if htmlgraph_plugin is a package)
166
+ plugin_resources = files("htmlgraph_plugin").joinpath(
167
+ ".claude-plugin/system-prompt-default.md"
168
+ )
169
+ if plugin_resources.is_file():
170
+ return plugin_resources.read_text(encoding="utf-8")
171
+ except Exception:
172
+ pass
173
+
174
+ # Try alternative path
175
+ try:
176
+ plugin_resources = files("htmlgraph").joinpath(
177
+ "plugin/.claude-plugin/system-prompt-default.md"
178
+ )
179
+ if plugin_resources.is_file():
180
+ return plugin_resources.read_text(encoding="utf-8")
181
+ except Exception:
182
+ pass
183
+ except Exception as e:
184
+ logger.debug(f"importlib.resources not available: {e}")
185
+
186
+ # Strategy 2: Direct file path (development and package installations)
187
+ try:
188
+ import htmlgraph
189
+
190
+ htmlgraph_path = Path(htmlgraph.__file__).parent
191
+ # Try relative paths from htmlgraph package
192
+ possible_paths = [
193
+ htmlgraph_path
194
+ / "plugin"
195
+ / ".claude-plugin"
196
+ / "system-prompt-default.md",
197
+ htmlgraph_path.parent
198
+ / "packages"
199
+ / "claude-plugin"
200
+ / ".claude-plugin"
201
+ / "system-prompt-default.md",
202
+ Path(__file__).parent.parent
203
+ / "packages"
204
+ / "claude-plugin"
205
+ / ".claude-plugin"
206
+ / "system-prompt-default.md",
207
+ ]
208
+
209
+ for path in possible_paths:
210
+ if path.exists():
211
+ try:
212
+ content = path.read_text(encoding="utf-8")
213
+ logger.info(f"Loaded plugin default from {path}")
214
+ return content
215
+ except Exception as e:
216
+ logger.debug(f"Failed to read {path}: {e}")
217
+ except Exception as e:
218
+ logger.debug(f"Could not load via htmlgraph package: {e}")
219
+
220
+ logger.debug("Plugin default system prompt not found")
221
+ return None
222
+
223
+ def get_project(self) -> str | None:
224
+ """
225
+ Get project-level system prompt override.
226
+
227
+ Looks for: `.claude/system-prompt.md`
228
+
229
+ Returns:
230
+ Project prompt text if exists, None otherwise
231
+
232
+ Raises:
233
+ RuntimeError: If file exists but cannot be read
234
+ """
235
+ prompt_file = self.claude_dir / "system-prompt.md"
236
+
237
+ if not prompt_file.exists():
238
+ return None
239
+
240
+ try:
241
+ content = prompt_file.read_text(encoding="utf-8")
242
+ logger.info(f"Loaded project system prompt ({len(content)} chars)")
243
+ return content
244
+ except Exception as e:
245
+ logger.error(f"Failed to read project system prompt: {e}")
246
+ raise RuntimeError(
247
+ f"Failed to read project system prompt at {prompt_file}: {e}"
248
+ )
249
+
250
+ def get_active(self) -> str | None:
251
+ """
252
+ Get active system prompt (project override OR plugin default).
253
+
254
+ Strategy:
255
+ 1. If `.claude/system-prompt.md` exists → use it (project override)
256
+ 2. Else if plugin default exists → use it
257
+ 3. Else → return None
258
+
259
+ Returns:
260
+ Active prompt text, or None if neither available
261
+
262
+ Note:
263
+ Project override always takes precedence over plugin default.
264
+ This allows teams to customize guidance while maintaining
265
+ a sensible default for users who haven't customized yet.
266
+ """
267
+ try:
268
+ project = self.get_project()
269
+ if project:
270
+ logger.info("Using project system prompt override")
271
+ return project
272
+ except RuntimeError:
273
+ # Project file exists but couldn't be read—log but continue
274
+ logger.warning("Could not read project prompt, falling back to default")
275
+
276
+ default = self.get_default()
277
+ if default:
278
+ logger.info("Using plugin default system prompt")
279
+ return default
280
+
281
+ logger.warning(
282
+ "No system prompt found (neither project override nor plugin default)"
283
+ )
284
+ return None
285
+
286
+ def create(
287
+ self,
288
+ template: str,
289
+ overwrite: bool = False,
290
+ ) -> SystemPromptManager:
291
+ """
292
+ Create or update project system prompt.
293
+
294
+ Creates `.claude/system-prompt.md` with provided template.
295
+
296
+ Args:
297
+ template: Prompt template text
298
+ overwrite: Whether to overwrite existing prompt (default: False)
299
+
300
+ Returns:
301
+ Self for method chaining
302
+
303
+ Raises:
304
+ RuntimeError: If file exists and overwrite=False, or if write fails
305
+
306
+ Example:
307
+ sdk = SDK(agent="claude")
308
+ sdk.system_prompts.create('''
309
+ # Team Rules
310
+ - Use TypeScript, not JavaScript
311
+ - All PRs need 2 approvals
312
+ ''')
313
+ """
314
+ self.claude_dir.mkdir(parents=True, exist_ok=True)
315
+ prompt_file = self.claude_dir / "system-prompt.md"
316
+
317
+ if prompt_file.exists() and not overwrite:
318
+ raise RuntimeError(
319
+ f"System prompt already exists at {prompt_file}. "
320
+ f"Use overwrite=True to replace, or delete the file first."
321
+ )
322
+
323
+ try:
324
+ prompt_file.write_text(template, encoding="utf-8")
325
+ logger.info(
326
+ f"Created system prompt at {prompt_file} ({len(template)} chars)"
327
+ )
328
+ except Exception as e:
329
+ raise RuntimeError(f"Failed to write system prompt at {prompt_file}: {e}")
330
+
331
+ return self
332
+
333
+ def validate(
334
+ self,
335
+ text: str | None = None,
336
+ max_tokens: int = 1000,
337
+ min_tokens: int = 50,
338
+ ) -> dict:
339
+ """
340
+ Validate a system prompt.
341
+
342
+ Args:
343
+ text: Prompt to validate (uses active prompt if None)
344
+ max_tokens: Maximum allowed tokens (default: 1000)
345
+ min_tokens: Minimum expected tokens (default: 50)
346
+
347
+ Returns:
348
+ Validation result dict with keys:
349
+ - is_valid: bool
350
+ - tokens: int
351
+ - warnings: List[str]
352
+ - message: str
353
+
354
+ Example:
355
+ result = sdk.system_prompts.validate()
356
+ print(result['message'])
357
+ if not result['is_valid']:
358
+ for warning in result['warnings']:
359
+ print(f" - {warning}")
360
+ """
361
+ prompt_text = text or self.get_active() or ""
362
+ return SystemPromptValidator.validate(
363
+ prompt_text,
364
+ max_tokens=max_tokens,
365
+ min_tokens=min_tokens,
366
+ )
367
+
368
+ def delete(self) -> bool:
369
+ """
370
+ Delete project system prompt override.
371
+
372
+ Removes `.claude/system-prompt.md` if it exists.
373
+ Falls back to plugin default on next session.
374
+
375
+ Returns:
376
+ True if file was deleted, False if didn't exist
377
+
378
+ Raises:
379
+ RuntimeError: If file exists but cannot be deleted
380
+
381
+ Example:
382
+ sdk = SDK(agent="claude")
383
+ if sdk.system_prompts.delete():
384
+ print("Deleted project prompt, using plugin default")
385
+ """
386
+ prompt_file = self.claude_dir / "system-prompt.md"
387
+
388
+ if not prompt_file.exists():
389
+ return False
390
+
391
+ try:
392
+ prompt_file.unlink()
393
+ logger.info(f"Deleted system prompt at {prompt_file}")
394
+ return True
395
+ except Exception as e:
396
+ raise RuntimeError(f"Failed to delete system prompt at {prompt_file}: {e}")
397
+
398
+ def get_stats(self) -> dict:
399
+ """
400
+ Get statistics about the system prompt.
401
+
402
+ Returns:
403
+ Dictionary with:
404
+ - source: "project_override" | "plugin_default" | "none"
405
+ - tokens: int
406
+ - bytes: int
407
+ - file_path: str | None
408
+
409
+ Example:
410
+ stats = sdk.system_prompts.get_stats()
411
+ print(f"Using {stats['source']}: {stats['tokens']} tokens")
412
+ """
413
+ prompt = self.get_active()
414
+
415
+ if not prompt:
416
+ return {
417
+ "source": "none",
418
+ "tokens": 0,
419
+ "bytes": 0,
420
+ "file_path": None,
421
+ }
422
+
423
+ # Determine source
424
+ project = self.get_project()
425
+ if project:
426
+ source = "project_override"
427
+ file_path = str(self.claude_dir / "system-prompt.md")
428
+ else:
429
+ source = "plugin_default"
430
+ file_path = None # Plugin default has no single path
431
+
432
+ return {
433
+ "source": source,
434
+ "tokens": SystemPromptValidator.count_tokens(prompt),
435
+ "bytes": len(prompt.encode("utf-8")),
436
+ "file_path": file_path,
437
+ }
438
+
439
+
440
+ # Integration with SDK
441
+ def _register_system_prompts_with_sdk() -> None:
442
+ """Register system_prompts property with SDK class.
443
+
444
+ This function is called during SDK initialization to add the
445
+ system_prompts property, enabling usage like:
446
+
447
+ sdk = SDK(agent="claude")
448
+ prompt = sdk.system_prompts.get_active()
449
+ """
450
+ pass # Integration handled via SDK property decorator