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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +67 -27
- htmlgraph/analytics_index.py +53 -20
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/deploy.py +26 -27
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {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
|