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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +173 -17
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +127 -0
- htmlgraph/agent_registry.py +45 -30
- htmlgraph/agents.py +160 -107
- htmlgraph/analytics/__init__.py +9 -2
- htmlgraph/analytics/cli.py +190 -51
- 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 +192 -100
- 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 +190 -14
- htmlgraph/analytics_index.py +135 -51
- 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 +208 -0
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/__init__.py +14 -0
- htmlgraph/builders/base.py +118 -29
- htmlgraph/builders/bug.py +150 -0
- htmlgraph/builders/chore.py +119 -0
- htmlgraph/builders/epic.py +150 -0
- htmlgraph/builders/feature.py +31 -6
- htmlgraph/builders/insight.py +195 -0
- htmlgraph/builders/metric.py +217 -0
- htmlgraph/builders/pattern.py +202 -0
- htmlgraph/builders/phase.py +162 -0
- htmlgraph/builders/spike.py +52 -19
- htmlgraph/builders/track.py +148 -72
- 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 +18 -0
- htmlgraph/collections/base.py +415 -98
- htmlgraph/collections/bug.py +53 -0
- htmlgraph/collections/chore.py +53 -0
- htmlgraph/collections/epic.py +53 -0
- htmlgraph/collections/feature.py +12 -26
- htmlgraph/collections/insight.py +100 -0
- htmlgraph/collections/metric.py +92 -0
- htmlgraph/collections/pattern.py +97 -0
- htmlgraph/collections/phase.py +53 -0
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +56 -16
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +511 -0
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +344 -0
- htmlgraph/converter.py +216 -28
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2406 -307
- 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 +19 -2
- htmlgraph/deploy.py +142 -125
- htmlgraph/deployment_models.py +474 -0
- 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 +182 -27
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +100 -52
- htmlgraph/event_migration.py +13 -4
- htmlgraph/exceptions.py +49 -0
- htmlgraph/file_watcher.py +101 -28
- htmlgraph/find_api.py +75 -63
- htmlgraph/git_events.py +145 -63
- htmlgraph/graph.py +1122 -106
- 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 +45 -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 +1314 -0
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/hooks-config.example.json +12 -0
- htmlgraph/hooks/installer.py +343 -0
- htmlgraph/hooks/orchestrator.py +674 -0
- htmlgraph/hooks/orchestrator_reflector.py +223 -0
- htmlgraph/hooks/post-checkout.sh +28 -0
- htmlgraph/hooks/post-commit.sh +24 -0
- htmlgraph/hooks/post-merge.sh +26 -0
- htmlgraph/hooks/post_tool_use_failure.py +273 -0
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +408 -0
- htmlgraph/hooks/pre-commit.sh +94 -0
- htmlgraph/hooks/pre-push.sh +28 -0
- htmlgraph/hooks/pretooluse.py +819 -0
- 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 +255 -0
- htmlgraph/hooks/task_validator.py +177 -0
- htmlgraph/hooks/validator.py +628 -0
- htmlgraph/ids.py +41 -27
- htmlgraph/index.d.ts +286 -0
- htmlgraph/learning.py +767 -0
- htmlgraph/mcp_server.py +69 -23
- htmlgraph/models.py +1586 -87
- 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/task_coordination.py +343 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +669 -0
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +328 -0
- htmlgraph/orchestrator_validator.py +133 -0
- htmlgraph/parallel.py +646 -0
- htmlgraph/parser.py +160 -35
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/planning.py +147 -52
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +109 -72
- 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/routing.py +8 -19
- htmlgraph/scripts/deploy.py +1 -2
- 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 +685 -180
- htmlgraph/services/__init__.py +10 -0
- htmlgraph/services/claiming.py +199 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +1392 -175
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +201 -0
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/setup.py +34 -17
- htmlgraph/spike_index.py +143 -0
- htmlgraph/sync_docs.py +12 -15
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph/templates/GEMINI.md.template +87 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +146 -15
- htmlgraph/track_manager.py +69 -21
- htmlgraph/transcript.py +890 -0
- htmlgraph/transcript_analytics.py +699 -0
- htmlgraph/types.py +323 -0
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +8 -5
- htmlgraph/work_type_utils.py +3 -2
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
- htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -2688
- htmlgraph/sdk.py +0 -709
- htmlgraph-0.9.3.dist-info/RECORD +0 -61
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger(__name__)
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Orchestrator Reflection Module
|
|
7
|
+
|
|
8
|
+
Detects when Claude executes Python code directly via Bash and provides
|
|
9
|
+
a gentle reflection prompt to encourage delegation to subagents.
|
|
10
|
+
|
|
11
|
+
This helps reinforce orchestrator patterns:
|
|
12
|
+
- Delegation over direct execution
|
|
13
|
+
- Parallel Task() calls for efficiency
|
|
14
|
+
- Work item tracking for all efforts
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
from htmlgraph.hooks.orchestrator_reflector import orchestrator_reflect
|
|
18
|
+
|
|
19
|
+
# In a posttooluse hook
|
|
20
|
+
hook_input = {
|
|
21
|
+
"tool_name": "Bash",
|
|
22
|
+
"tool_input": {"command": "uv run pytest"}
|
|
23
|
+
}
|
|
24
|
+
result = orchestrator_reflect(hook_input)
|
|
25
|
+
# Returns: {"continue": True, "hookSpecificOutput": {...}}
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import re
|
|
29
|
+
from typing import Any, TypedDict
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class HookSpecificOutput(TypedDict):
|
|
33
|
+
"""Hook-specific output structure."""
|
|
34
|
+
|
|
35
|
+
hookEventName: str
|
|
36
|
+
additionalContext: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class HookResponse(TypedDict):
|
|
40
|
+
"""Hook response structure."""
|
|
41
|
+
|
|
42
|
+
continue_: bool
|
|
43
|
+
hookSpecificOutput: HookSpecificOutput | None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Python execution patterns to detect
|
|
47
|
+
PYTHON_EXECUTION_PATTERNS = [
|
|
48
|
+
r"\buv\s+run\b", # uv run <anything>
|
|
49
|
+
r"\bpython\s+-c\b", # python -c "code"
|
|
50
|
+
r"\bpython\s+[\w/.-]+\.py\b", # python script.py
|
|
51
|
+
r"\bpython\s+-m\s+\w+", # python -m module
|
|
52
|
+
r"\bpytest\b", # pytest
|
|
53
|
+
r"\bpython3\s+", # python3 command
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
# Commands to exclude from detection
|
|
57
|
+
EXCLUDED_COMMAND_PREFIXES = ("git ", " git ", "ls ", "cat ", "grep ", "find ")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def is_python_execution(command: str) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Detect if a bash command is executing Python code.
|
|
63
|
+
|
|
64
|
+
Patterns to detect:
|
|
65
|
+
- uv run <script>
|
|
66
|
+
- python -c <code>
|
|
67
|
+
- python <script>
|
|
68
|
+
- pytest
|
|
69
|
+
- python -m <module>
|
|
70
|
+
|
|
71
|
+
Excludes:
|
|
72
|
+
- git commands (even if they mention python)
|
|
73
|
+
- simple tool calls that happen to have "python" in path
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
command: The bash command to analyze
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
True if the command executes Python code
|
|
80
|
+
"""
|
|
81
|
+
# Normalize command
|
|
82
|
+
cmd = command.strip().lower()
|
|
83
|
+
|
|
84
|
+
# Exclude git commands entirely
|
|
85
|
+
if cmd.startswith("git ") or " git " in cmd:
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
# Exclude simple file operations
|
|
89
|
+
if cmd.startswith(EXCLUDED_COMMAND_PREFIXES):
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
# Check for Python execution patterns
|
|
93
|
+
for pattern in PYTHON_EXECUTION_PATTERNS:
|
|
94
|
+
if re.search(pattern, cmd):
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def should_reflect(hook_input: dict[str, Any]) -> tuple[bool, str]:
|
|
101
|
+
"""
|
|
102
|
+
Check if we should show reflection prompt.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
hook_input: The hook input data containing tool name and tool input
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
(should_show, command_preview) tuple where:
|
|
109
|
+
- should_show: True if reflection should be shown
|
|
110
|
+
- command_preview: Preview of the command (first 60 chars)
|
|
111
|
+
"""
|
|
112
|
+
tool_name = hook_input.get("tool_name", "")
|
|
113
|
+
|
|
114
|
+
# Only check Bash tool usage
|
|
115
|
+
if tool_name != "Bash":
|
|
116
|
+
return False, ""
|
|
117
|
+
|
|
118
|
+
# Get the command
|
|
119
|
+
tool_input = hook_input.get("tool_input", {})
|
|
120
|
+
command = tool_input.get("command", "")
|
|
121
|
+
|
|
122
|
+
if not command:
|
|
123
|
+
return False, ""
|
|
124
|
+
|
|
125
|
+
# Check if it's Python execution
|
|
126
|
+
if is_python_execution(command):
|
|
127
|
+
# Create a preview of the command (first 60 chars)
|
|
128
|
+
preview = command[:60].replace("\n", " ")
|
|
129
|
+
if len(command) > 60:
|
|
130
|
+
preview += "..."
|
|
131
|
+
return True, preview
|
|
132
|
+
|
|
133
|
+
return False, ""
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def build_reflection_message(command_preview: str) -> str:
|
|
137
|
+
"""
|
|
138
|
+
Build the reflection message for orchestrator.
|
|
139
|
+
|
|
140
|
+
This should be:
|
|
141
|
+
- Gentle and non-blocking
|
|
142
|
+
- Encourage reflection without being preachy
|
|
143
|
+
- Point to specific alternatives
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
command_preview: Preview of the executed command
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
The formatted reflection message
|
|
150
|
+
"""
|
|
151
|
+
return f"""ORCHESTRATOR REFLECTION: You executed code directly.
|
|
152
|
+
|
|
153
|
+
Command: {command_preview}
|
|
154
|
+
|
|
155
|
+
Ask yourself:
|
|
156
|
+
- Could this have been delegated to a subagent?
|
|
157
|
+
- Would parallel Task() calls have been faster?
|
|
158
|
+
- Is a work item tracking this effort?
|
|
159
|
+
|
|
160
|
+
Continue, but consider delegation for similar future tasks."""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def orchestrator_reflect(tool_input: dict[str, Any]) -> dict[str, Any]:
|
|
164
|
+
"""
|
|
165
|
+
Main API function for orchestrator reflection.
|
|
166
|
+
|
|
167
|
+
Analyzes tool usage and provides reflection feedback when direct
|
|
168
|
+
Python execution is detected.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
tool_input: Hook input containing tool_name and tool_input fields
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Hook response dict with continue=True and optional hookSpecificOutput
|
|
175
|
+
|
|
176
|
+
Example:
|
|
177
|
+
>>> hook_input = {
|
|
178
|
+
... "tool_name": "Bash",
|
|
179
|
+
... "tool_input": {"command": "uv run pytest"}
|
|
180
|
+
... }
|
|
181
|
+
>>> result = orchestrator_reflect(hook_input)
|
|
182
|
+
>>> result["continue"]
|
|
183
|
+
True
|
|
184
|
+
>>> "hookSpecificOutput" in result
|
|
185
|
+
True
|
|
186
|
+
"""
|
|
187
|
+
# Check if we should reflect
|
|
188
|
+
should_show, command_preview = should_reflect(tool_input)
|
|
189
|
+
|
|
190
|
+
# Build response
|
|
191
|
+
response: dict[str, Any] = {"continue": True}
|
|
192
|
+
|
|
193
|
+
if should_show:
|
|
194
|
+
reflection = build_reflection_message(command_preview)
|
|
195
|
+
response["hookSpecificOutput"] = {
|
|
196
|
+
"hookEventName": "PostToolUse",
|
|
197
|
+
"additionalContext": reflection,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return response
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def main() -> None:
|
|
204
|
+
"""Hook entry point for script wrapper."""
|
|
205
|
+
import json
|
|
206
|
+
import os
|
|
207
|
+
import sys
|
|
208
|
+
|
|
209
|
+
# Check if tracking is disabled
|
|
210
|
+
if os.environ.get("HTMLGRAPH_DISABLE_TRACKING") == "1":
|
|
211
|
+
print(json.dumps({"continue": True}))
|
|
212
|
+
sys.exit(0)
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
hook_input = json.load(sys.stdin)
|
|
216
|
+
except json.JSONDecodeError:
|
|
217
|
+
hook_input = {}
|
|
218
|
+
|
|
219
|
+
# Run reflection logic
|
|
220
|
+
response = orchestrator_reflect(hook_input)
|
|
221
|
+
|
|
222
|
+
# Output JSON response
|
|
223
|
+
print(json.dumps(response))
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# HtmlGraph Post-Checkout Hook
|
|
4
|
+
# Logs branch switches / checkouts for continuity tracking
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
set +e
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
10
|
+
cd "$PROJECT_ROOT" || exit 0
|
|
11
|
+
|
|
12
|
+
if [ ! -d ".htmlgraph" ]; then
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
OLD_HEAD="$1"
|
|
17
|
+
NEW_HEAD="$2"
|
|
18
|
+
FLAG="$3"
|
|
19
|
+
|
|
20
|
+
if ! command -v htmlgraph &> /dev/null; then
|
|
21
|
+
if command -v python3 &> /dev/null; then
|
|
22
|
+
python3 -m htmlgraph.git_events checkout "$OLD_HEAD" "$NEW_HEAD" "$FLAG" &> /dev/null &
|
|
23
|
+
fi
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
htmlgraph git-event checkout "$OLD_HEAD" "$NEW_HEAD" "$FLAG" &> /dev/null &
|
|
28
|
+
exit 0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# HtmlGraph Post-Commit Hook
|
|
4
|
+
# Logs Git commit events for agent-agnostic continuity tracking
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
set +e
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
10
|
+
cd "$PROJECT_ROOT" || exit 0
|
|
11
|
+
|
|
12
|
+
if [ ! -d ".htmlgraph" ]; then
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if ! command -v htmlgraph &> /dev/null; then
|
|
17
|
+
if command -v python3 &> /dev/null; then
|
|
18
|
+
python3 -m htmlgraph.git_events commit &> /dev/null &
|
|
19
|
+
fi
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
htmlgraph git-event commit &> /dev/null &
|
|
24
|
+
exit 0
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# HtmlGraph Post-Merge Hook
|
|
4
|
+
# Logs successful merges for continuity tracking
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
set +e
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
10
|
+
cd "$PROJECT_ROOT" || exit 0
|
|
11
|
+
|
|
12
|
+
if [ ! -d ".htmlgraph" ]; then
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
SQUASH_FLAG="$1"
|
|
17
|
+
|
|
18
|
+
if ! command -v htmlgraph &> /dev/null; then
|
|
19
|
+
if command -v python3 &> /dev/null; then
|
|
20
|
+
python3 -m htmlgraph.git_events merge "$SQUASH_FLAG" &> /dev/null &
|
|
21
|
+
fi
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
htmlgraph git-event merge "$SQUASH_FLAG" &> /dev/null &
|
|
26
|
+
exit 0
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
PostToolUseFailure Hook - Automatic Error Tracking and Debug Spike Creation
|
|
8
|
+
|
|
9
|
+
This hook is triggered when tool executions fail, enabling:
|
|
10
|
+
- Error logging to .htmlgraph/errors.jsonl
|
|
11
|
+
- Pattern detection for recurring errors (3+ occurrences)
|
|
12
|
+
- Automatic debug spike creation for investigation
|
|
13
|
+
- Error context preservation for debugging
|
|
14
|
+
|
|
15
|
+
CRITICAL REQUIREMENTS:
|
|
16
|
+
- MUST exit with code 0 (exit 1 blocks Claude)
|
|
17
|
+
- MUST execute quickly (< 1 second)
|
|
18
|
+
- MUST use file-based output (not stdout)
|
|
19
|
+
- MUST handle all exceptions gracefully
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def run(hook_input: dict[str, Any]) -> dict[str, Any]:
|
|
31
|
+
"""
|
|
32
|
+
Handle tool execution failures.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
hook_input: Hook input containing:
|
|
36
|
+
- name: Tool name that failed
|
|
37
|
+
- result: Tool result (may contain error field)
|
|
38
|
+
- session_id: Current session ID
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Standard hook response: {"continue": True}
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
# DEBUG: Log raw hook input to understand structure
|
|
45
|
+
debug_log = Path(".htmlgraph/hook-debug.jsonl")
|
|
46
|
+
debug_log.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
with open(debug_log, "a") as f:
|
|
48
|
+
f.write(
|
|
49
|
+
json.dumps(
|
|
50
|
+
{
|
|
51
|
+
"raw_input": hook_input,
|
|
52
|
+
"keys": list(hook_input.keys()),
|
|
53
|
+
"ts": datetime.now().isoformat(),
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
+ "\n"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Extract error information from PostToolUse hook format
|
|
60
|
+
# Official PostToolUse uses: tool_name, tool_response
|
|
61
|
+
# Custom hooks may use: name, result
|
|
62
|
+
tool_name = hook_input.get("tool_name") or hook_input.get("name", "unknown")
|
|
63
|
+
session_id = hook_input.get("session_id", "unknown")
|
|
64
|
+
|
|
65
|
+
# Error message can be in different places depending on tool
|
|
66
|
+
error_msg = "No error message"
|
|
67
|
+
|
|
68
|
+
# Check tool_response field first (official PostToolUse format)
|
|
69
|
+
# Then check result field (custom hook format)
|
|
70
|
+
result = hook_input.get("tool_response") or hook_input.get("result", {})
|
|
71
|
+
if isinstance(result, dict):
|
|
72
|
+
if "error" in result:
|
|
73
|
+
error_msg = result["error"]
|
|
74
|
+
elif "message" in result:
|
|
75
|
+
error_msg = result["message"]
|
|
76
|
+
elif isinstance(result, str):
|
|
77
|
+
# Sometimes the error is directly in the result as a string
|
|
78
|
+
error_msg = result
|
|
79
|
+
|
|
80
|
+
# Fallback: check top-level error field
|
|
81
|
+
if error_msg == "No error message" and "error" in hook_input:
|
|
82
|
+
error_msg = hook_input["error"]
|
|
83
|
+
|
|
84
|
+
# Last resort: stringify the result if it contains error indicators
|
|
85
|
+
if error_msg == "No error message" and result:
|
|
86
|
+
result_str = str(result).lower()
|
|
87
|
+
if any(
|
|
88
|
+
indicator in result_str
|
|
89
|
+
for indicator in ["error", "failed", "exception"]
|
|
90
|
+
):
|
|
91
|
+
error_msg = str(result)[:500] # Truncate to 500 chars
|
|
92
|
+
|
|
93
|
+
# Track error in file-based storage
|
|
94
|
+
error_log = Path(".htmlgraph/errors.jsonl")
|
|
95
|
+
error_log.parent.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
error_entry = {
|
|
98
|
+
"timestamp": datetime.now().isoformat(),
|
|
99
|
+
"tool": tool_name,
|
|
100
|
+
"error": error_msg,
|
|
101
|
+
"session_id": session_id,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
with open(error_log, "a") as f:
|
|
105
|
+
f.write(json.dumps(error_entry) + "\n")
|
|
106
|
+
|
|
107
|
+
# Check for error patterns (same error 3+ times)
|
|
108
|
+
if should_create_debug_spike(tool_name, error_msg, error_log):
|
|
109
|
+
create_debug_spike(tool_name, error_msg, error_log)
|
|
110
|
+
|
|
111
|
+
# Return success (don't block Claude)
|
|
112
|
+
return {"continue": True}
|
|
113
|
+
|
|
114
|
+
except Exception as e:
|
|
115
|
+
# Never raise - log and continue
|
|
116
|
+
logger.warning(f"PostToolUseFailure hook error: {e}")
|
|
117
|
+
return {"continue": True}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def should_create_debug_spike(tool: str, error: str, log_path: Path) -> bool:
|
|
121
|
+
"""
|
|
122
|
+
Check if this error has occurred 3+ times.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
tool: Tool name that failed
|
|
126
|
+
error: Error message
|
|
127
|
+
log_path: Path to errors.jsonl
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
True if error occurred 3+ times, False otherwise
|
|
131
|
+
"""
|
|
132
|
+
if not log_path.exists():
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
count = 0
|
|
136
|
+
# Use first 100 chars of error for pattern matching
|
|
137
|
+
error_signature = error[:100]
|
|
138
|
+
|
|
139
|
+
with open(log_path) as f:
|
|
140
|
+
for line in f:
|
|
141
|
+
try:
|
|
142
|
+
entry = json.loads(line)
|
|
143
|
+
if entry.get("tool") == tool and error_signature in entry.get(
|
|
144
|
+
"error", ""
|
|
145
|
+
):
|
|
146
|
+
count += 1
|
|
147
|
+
if count >= 3:
|
|
148
|
+
return True
|
|
149
|
+
except Exception:
|
|
150
|
+
continue
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def create_debug_spike(tool: str, error: str, log_path: Path) -> None:
|
|
155
|
+
"""
|
|
156
|
+
Auto-create debug spike for recurring error.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
tool: Tool name that failed
|
|
160
|
+
error: Error message
|
|
161
|
+
log_path: Path to errors.jsonl
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
from htmlgraph import SDK
|
|
165
|
+
|
|
166
|
+
sdk = SDK(agent="error-tracker")
|
|
167
|
+
|
|
168
|
+
# Get recent occurrences
|
|
169
|
+
occurrences = []
|
|
170
|
+
error_signature = error[:100]
|
|
171
|
+
with open(log_path) as f:
|
|
172
|
+
for line in f:
|
|
173
|
+
try:
|
|
174
|
+
entry = json.loads(line)
|
|
175
|
+
if entry.get("tool") == tool and error_signature in entry.get(
|
|
176
|
+
"error", ""
|
|
177
|
+
):
|
|
178
|
+
occurrences.append(entry)
|
|
179
|
+
except Exception:
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
# Check if spike already exists for this error
|
|
183
|
+
spike_marker = Path(".htmlgraph/.error-spikes.json")
|
|
184
|
+
existing_spikes = {}
|
|
185
|
+
if spike_marker.exists():
|
|
186
|
+
try:
|
|
187
|
+
with open(spike_marker) as f:
|
|
188
|
+
existing_spikes = json.load(f)
|
|
189
|
+
except Exception:
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
# Create unique key for this error
|
|
193
|
+
error_key = f"{tool}:{error_signature}"
|
|
194
|
+
if error_key in existing_spikes:
|
|
195
|
+
# Already created spike for this error
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
# Create debug spike
|
|
199
|
+
spike = (
|
|
200
|
+
sdk.spikes.create(f"Recurring Error: {tool}")
|
|
201
|
+
.set_spike_type("technical")
|
|
202
|
+
.set_findings(
|
|
203
|
+
f"""
|
|
204
|
+
## Recurring Tool Failure Detected
|
|
205
|
+
|
|
206
|
+
**Tool**: {tool}
|
|
207
|
+
**Occurrences**: {len(occurrences)}
|
|
208
|
+
**First Seen**: {occurrences[0]["timestamp"] if occurrences else "unknown"}
|
|
209
|
+
**Last Seen**: {occurrences[-1]["timestamp"] if occurrences else "unknown"}
|
|
210
|
+
|
|
211
|
+
### Error Message
|
|
212
|
+
```
|
|
213
|
+
{error}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Recent Occurrences
|
|
217
|
+
{chr(10).join(f"- {o['timestamp']}: {o.get('session_id', 'unknown')}" for o in occurrences[-5:])}
|
|
218
|
+
|
|
219
|
+
### Recommended Actions
|
|
220
|
+
1. Review error message for root cause
|
|
221
|
+
2. Check if this is a known issue in the codebase
|
|
222
|
+
3. Search GitHub issues if Claude Code related
|
|
223
|
+
4. Fix underlying issue or add error handling
|
|
224
|
+
5. Test fix to ensure error doesn't recur
|
|
225
|
+
|
|
226
|
+
### Debugging Resources
|
|
227
|
+
- `.htmlgraph/errors.jsonl` - Full error log
|
|
228
|
+
- `DEBUGGING.md` - Systematic debugging guide
|
|
229
|
+
- `/doctor` - System diagnostics
|
|
230
|
+
- `claude --debug` - Verbose output
|
|
231
|
+
"""
|
|
232
|
+
)
|
|
233
|
+
.save()
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Record that we created a spike for this error
|
|
237
|
+
existing_spikes[error_key] = {
|
|
238
|
+
"spike_id": spike.id,
|
|
239
|
+
"created": datetime.now().isoformat(),
|
|
240
|
+
"occurrences": len(occurrences),
|
|
241
|
+
}
|
|
242
|
+
with open(spike_marker, "w") as f:
|
|
243
|
+
json.dump(existing_spikes, f, indent=2)
|
|
244
|
+
|
|
245
|
+
logger.warning(f"Created debug spike: {spike.id}")
|
|
246
|
+
|
|
247
|
+
except Exception as e:
|
|
248
|
+
logger.warning(f"Failed to create debug spike: {e}")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def main() -> None:
|
|
252
|
+
"""Hook entry point for script wrapper."""
|
|
253
|
+
# Check environment override
|
|
254
|
+
if os.environ.get("HTMLGRAPH_DISABLE_TRACKING") == "1":
|
|
255
|
+
print(json.dumps({"continue": True}))
|
|
256
|
+
sys.exit(0)
|
|
257
|
+
|
|
258
|
+
# Read tool input from stdin
|
|
259
|
+
try:
|
|
260
|
+
hook_input = json.load(sys.stdin)
|
|
261
|
+
except json.JSONDecodeError:
|
|
262
|
+
hook_input = {}
|
|
263
|
+
|
|
264
|
+
# Run hook
|
|
265
|
+
result = run(hook_input)
|
|
266
|
+
|
|
267
|
+
# Output response
|
|
268
|
+
print(json.dumps(result))
|
|
269
|
+
sys.exit(0) # Always exit 0
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
if __name__ == "__main__":
|
|
273
|
+
main()
|