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,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"updated": "2026-01-10T08:55:56.503507",
|
|
4
|
+
"agents": {
|
|
5
|
+
"claude": {
|
|
6
|
+
"id": "claude",
|
|
7
|
+
"name": "Claude",
|
|
8
|
+
"capabilities": [
|
|
9
|
+
"python",
|
|
10
|
+
"javascript",
|
|
11
|
+
"typescript",
|
|
12
|
+
"html",
|
|
13
|
+
"css",
|
|
14
|
+
"code-review",
|
|
15
|
+
"testing",
|
|
16
|
+
"documentation",
|
|
17
|
+
"debugging",
|
|
18
|
+
"refactoring",
|
|
19
|
+
"architecture",
|
|
20
|
+
"api-design"
|
|
21
|
+
],
|
|
22
|
+
"max_parallel_tasks": 3,
|
|
23
|
+
"preferred_complexity": [
|
|
24
|
+
"low",
|
|
25
|
+
"medium",
|
|
26
|
+
"high",
|
|
27
|
+
"very-high"
|
|
28
|
+
],
|
|
29
|
+
"active": true,
|
|
30
|
+
"metadata": {}
|
|
31
|
+
},
|
|
32
|
+
"gemini": {
|
|
33
|
+
"id": "gemini",
|
|
34
|
+
"name": "Gemini",
|
|
35
|
+
"capabilities": [
|
|
36
|
+
"python",
|
|
37
|
+
"data-analysis",
|
|
38
|
+
"documentation",
|
|
39
|
+
"testing",
|
|
40
|
+
"code-review",
|
|
41
|
+
"javascript"
|
|
42
|
+
],
|
|
43
|
+
"max_parallel_tasks": 2,
|
|
44
|
+
"preferred_complexity": [
|
|
45
|
+
"low",
|
|
46
|
+
"medium",
|
|
47
|
+
"high"
|
|
48
|
+
],
|
|
49
|
+
"active": true,
|
|
50
|
+
"metadata": {}
|
|
51
|
+
},
|
|
52
|
+
"codex": {
|
|
53
|
+
"id": "codex",
|
|
54
|
+
"name": "Codex",
|
|
55
|
+
"capabilities": [
|
|
56
|
+
"python",
|
|
57
|
+
"javascript",
|
|
58
|
+
"debugging",
|
|
59
|
+
"testing",
|
|
60
|
+
"code-generation",
|
|
61
|
+
"documentation"
|
|
62
|
+
],
|
|
63
|
+
"max_parallel_tasks": 2,
|
|
64
|
+
"preferred_complexity": [
|
|
65
|
+
"low",
|
|
66
|
+
"medium"
|
|
67
|
+
],
|
|
68
|
+
"active": true,
|
|
69
|
+
"metadata": {}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HtmlGraph hooks package.
|
|
3
|
+
|
|
4
|
+
This package contains the hook logic for HtmlGraph tracking integration
|
|
5
|
+
with Claude Code and other AI coding assistants.
|
|
6
|
+
|
|
7
|
+
All hooks use a unified architecture:
|
|
8
|
+
- Logic lives in package modules (not scripts)
|
|
9
|
+
- Parallel execution where possible (asyncio.gather)
|
|
10
|
+
- Unified error handling and response format
|
|
11
|
+
- Easy testing via direct imports
|
|
12
|
+
- Deployed via package updates
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from htmlgraph.hooks.posttooluse import posttooluse_hook
|
|
18
|
+
from htmlgraph.hooks.pretooluse import pretooluse_hook
|
|
19
|
+
from htmlgraph.hooks.state_manager import (
|
|
20
|
+
DriftQueueManager,
|
|
21
|
+
ParentActivityTracker,
|
|
22
|
+
UserQueryEventTracker,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Directory containing hook scripts
|
|
26
|
+
HOOKS_DIR = Path(__file__).parent
|
|
27
|
+
|
|
28
|
+
# Git hooks that can be installed
|
|
29
|
+
AVAILABLE_HOOKS = [
|
|
30
|
+
"pre-commit",
|
|
31
|
+
"post-commit",
|
|
32
|
+
"pre-push",
|
|
33
|
+
"post-checkout",
|
|
34
|
+
"post-merge",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"pretooluse_hook",
|
|
39
|
+
"posttooluse_hook",
|
|
40
|
+
"ParentActivityTracker",
|
|
41
|
+
"UserQueryEventTracker",
|
|
42
|
+
"DriftQueueManager",
|
|
43
|
+
"AVAILABLE_HOOKS",
|
|
44
|
+
"HOOKS_DIR",
|
|
45
|
+
]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Bootstrap utilities for hook scripts.
|
|
3
|
+
|
|
4
|
+
Centralizes environment setup and project directory resolution used by all hooks.
|
|
5
|
+
Handles both development (src/python) and installed (package) modes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def resolve_project_dir(cwd: str | None = None) -> str:
|
|
16
|
+
"""Resolve the project directory with sensible fallbacks.
|
|
17
|
+
|
|
18
|
+
Hierarchy:
|
|
19
|
+
1. CLAUDE_PROJECT_DIR environment variable (set by Claude Code)
|
|
20
|
+
2. Git repository root (via git rev-parse --show-toplevel)
|
|
21
|
+
3. Current working directory (or provided cwd)
|
|
22
|
+
|
|
23
|
+
This supports running hooks in multiple contexts:
|
|
24
|
+
- Within a Claude Code session
|
|
25
|
+
- In git repositories
|
|
26
|
+
- In arbitrary directories
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
cwd: Starting directory for git search. Defaults to os.getcwd().
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Absolute path to the project directory.
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
No exceptions - always returns a valid path.
|
|
36
|
+
"""
|
|
37
|
+
# First priority: Claude's explicit project directory
|
|
38
|
+
env_dir = os.environ.get("CLAUDE_PROJECT_DIR")
|
|
39
|
+
if env_dir:
|
|
40
|
+
return env_dir
|
|
41
|
+
|
|
42
|
+
# Second priority: Git repository root
|
|
43
|
+
start_dir = cwd or os.getcwd()
|
|
44
|
+
try:
|
|
45
|
+
result = subprocess.run(
|
|
46
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
47
|
+
capture_output=True,
|
|
48
|
+
text=True,
|
|
49
|
+
cwd=start_dir,
|
|
50
|
+
timeout=5,
|
|
51
|
+
)
|
|
52
|
+
if result.returncode == 0:
|
|
53
|
+
return result.stdout.strip()
|
|
54
|
+
except Exception:
|
|
55
|
+
# Git not available or not a repo - continue to fallback
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
# Final fallback: current working directory
|
|
59
|
+
return start_dir
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def bootstrap_pythonpath(project_dir: str) -> None:
|
|
63
|
+
"""Bootstrap Python path for htmlgraph imports.
|
|
64
|
+
|
|
65
|
+
Handles two common deployment modes:
|
|
66
|
+
1. Development: Running inside htmlgraph repository (src/python exists)
|
|
67
|
+
2. Installed: Running where htmlgraph is installed as a package (do nothing)
|
|
68
|
+
|
|
69
|
+
This allows hooks to work correctly whether htmlgraph is:
|
|
70
|
+
- Being developed locally (add src/python to path)
|
|
71
|
+
- Installed in a virtual environment (already in path)
|
|
72
|
+
- Installed globally (already in path)
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
project_dir: Project directory from resolve_project_dir().
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
None (modifies sys.path in-place).
|
|
79
|
+
|
|
80
|
+
Side Effects:
|
|
81
|
+
- Modifies sys.path to ensure htmlgraph is importable
|
|
82
|
+
- Adds .venv/lib/pythonX.Y/site-packages if virtual environment exists
|
|
83
|
+
- Adds src/python if in htmlgraph repository
|
|
84
|
+
"""
|
|
85
|
+
project_path = Path(project_dir)
|
|
86
|
+
|
|
87
|
+
# First, try to use local virtual environment if it exists
|
|
88
|
+
venv = project_path / ".venv"
|
|
89
|
+
if venv.exists():
|
|
90
|
+
pyver = f"python{sys.version_info.major}.{sys.version_info.minor}"
|
|
91
|
+
candidates = [
|
|
92
|
+
venv / "lib" / pyver / "site-packages", # macOS/Linux
|
|
93
|
+
venv / "Lib" / "site-packages", # Windows
|
|
94
|
+
]
|
|
95
|
+
for candidate in candidates:
|
|
96
|
+
if candidate.exists():
|
|
97
|
+
sys.path.insert(0, str(candidate))
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
# Then, add src/python if this is the htmlgraph repository itself
|
|
101
|
+
repo_src = project_path / "src" / "python"
|
|
102
|
+
if repo_src.exists():
|
|
103
|
+
sys.path.insert(0, str(repo_src))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_graph_dir(cwd: str | None = None) -> Path:
|
|
107
|
+
"""Get the .htmlgraph directory path, creating it if necessary.
|
|
108
|
+
|
|
109
|
+
The .htmlgraph directory is the root for all HtmlGraph tracking:
|
|
110
|
+
- .htmlgraph/sessions/ - Session HTML files
|
|
111
|
+
- .htmlgraph/features/ - Feature tracking
|
|
112
|
+
- .htmlgraph/events/ - Event JSON files
|
|
113
|
+
- .htmlgraph/htmlgraph.db - SQLite database
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
cwd: Starting directory for project resolution. Defaults to os.getcwd().
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Path to the .htmlgraph directory (guaranteed to exist).
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
OSError: If directory creation fails (e.g., permission denied).
|
|
123
|
+
"""
|
|
124
|
+
project_dir = resolve_project_dir(cwd)
|
|
125
|
+
graph_dir = Path(project_dir) / ".htmlgraph"
|
|
126
|
+
graph_dir.mkdir(parents=True, exist_ok=True)
|
|
127
|
+
return graph_dir
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def init_logger(name: str) -> logging.Logger:
|
|
131
|
+
"""Initialize a logger with standardized configuration.
|
|
132
|
+
|
|
133
|
+
Sets up a logger for hook scripts with:
|
|
134
|
+
- Consistent format across all hooks
|
|
135
|
+
- basicConfig applied only once (subsequent calls are ignored)
|
|
136
|
+
- Named logger returned (can be used for filtering)
|
|
137
|
+
|
|
138
|
+
Format: "[TIMESTAMP] [LEVEL] [logger_name] message"
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
name: Logger name (typically __name__ from calling module).
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
logging.Logger instance configured and ready to use.
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
```python
|
|
148
|
+
logger = init_logger(__name__)
|
|
149
|
+
logger.info("Hook started")
|
|
150
|
+
logger.error("Something went wrong")
|
|
151
|
+
```
|
|
152
|
+
"""
|
|
153
|
+
# Configure basicConfig only once (subsequent calls are no-ops)
|
|
154
|
+
logging.basicConfig(
|
|
155
|
+
level=logging.INFO,
|
|
156
|
+
format="[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s",
|
|
157
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Return named logger for this module
|
|
161
|
+
return logging.getLogger(name)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
__all__ = [
|
|
165
|
+
"resolve_project_dir",
|
|
166
|
+
"bootstrap_pythonpath",
|
|
167
|
+
"get_graph_dir",
|
|
168
|
+
"init_logger",
|
|
169
|
+
]
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger(__name__)
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
CIGS PreToolUse Enforcer - Enhanced Orchestrator Enforcement with Escalation
|
|
7
|
+
|
|
8
|
+
Integrates the Computational Imperative Guidance System (CIGS) into the PreToolUse
|
|
9
|
+
hook for intelligent delegation enforcement with escalating guidance.
|
|
10
|
+
|
|
11
|
+
Architecture:
|
|
12
|
+
1. Uses existing OrchestratorValidator for base classification
|
|
13
|
+
2. Loads session violation count from ViolationTracker
|
|
14
|
+
3. Classifies operation using CostCalculator
|
|
15
|
+
4. Generates imperative message with escalation via ImperativeMessageGenerator
|
|
16
|
+
5. Records violation if should_delegate=True
|
|
17
|
+
6. Returns hookSpecificOutput with imperative message
|
|
18
|
+
|
|
19
|
+
Escalation Levels:
|
|
20
|
+
- Level 0 (0 violations): Guidance - informative, no cost shown
|
|
21
|
+
- Level 1 (1 violation): Imperative - commanding, includes cost
|
|
22
|
+
- Level 2 (2 violations): Final Warning - urgent, includes consequences
|
|
23
|
+
- Level 3 (3+ violations): Circuit Breaker - blocking, requires acknowledgment
|
|
24
|
+
|
|
25
|
+
Design Reference:
|
|
26
|
+
.htmlgraph/spikes/computational-imperative-guidance-system-design.md
|
|
27
|
+
Part 2: CIGS PreToolUse Hook Integration
|
|
28
|
+
Part 4: Imperative Message Generation
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import sys
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Any
|
|
36
|
+
|
|
37
|
+
from htmlgraph.cigs.cost import CostCalculator
|
|
38
|
+
from htmlgraph.cigs.messaging import ImperativeMessageGenerator
|
|
39
|
+
from htmlgraph.cigs.tracker import ViolationTracker
|
|
40
|
+
from htmlgraph.hooks.orchestrator import is_allowed_orchestrator_operation
|
|
41
|
+
from htmlgraph.orchestrator_mode import OrchestratorModeManager
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CIGSPreToolEnforcer:
|
|
45
|
+
"""
|
|
46
|
+
CIGS-enhanced PreToolUse enforcement with escalating imperative messages.
|
|
47
|
+
|
|
48
|
+
Integrates all CIGS components for comprehensive delegation enforcement.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# Tools that are ALWAYS allowed (orchestrator core)
|
|
52
|
+
ALWAYS_ALLOWED = {"Task", "AskUserQuestion", "TodoWrite"}
|
|
53
|
+
|
|
54
|
+
# Exploration tools that require delegation after first use
|
|
55
|
+
EXPLORATION_TOOLS = {"Read", "Grep", "Glob"}
|
|
56
|
+
|
|
57
|
+
# Implementation tools that always require delegation
|
|
58
|
+
IMPLEMENTATION_TOOLS = {"Edit", "Write", "NotebookEdit", "Delete"}
|
|
59
|
+
|
|
60
|
+
def __init__(self, graph_dir: Path | None = None):
|
|
61
|
+
"""
|
|
62
|
+
Initialize CIGS PreToolUse enforcer.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
graph_dir: Root directory for HtmlGraph (defaults to .htmlgraph)
|
|
66
|
+
"""
|
|
67
|
+
if graph_dir is None:
|
|
68
|
+
graph_dir = self._find_graph_dir()
|
|
69
|
+
|
|
70
|
+
self.graph_dir = graph_dir
|
|
71
|
+
self.manager = OrchestratorModeManager(graph_dir)
|
|
72
|
+
self.cost_calculator = CostCalculator()
|
|
73
|
+
self.message_generator = ImperativeMessageGenerator()
|
|
74
|
+
self.tracker = ViolationTracker(graph_dir)
|
|
75
|
+
|
|
76
|
+
# Ensure session ID is set (detect from environment or use current session)
|
|
77
|
+
if self.tracker._session_id is None:
|
|
78
|
+
self.tracker.set_session_id(self._get_or_create_session_id())
|
|
79
|
+
|
|
80
|
+
def _find_graph_dir(self) -> Path:
|
|
81
|
+
"""Find .htmlgraph directory starting from cwd."""
|
|
82
|
+
cwd = Path.cwd()
|
|
83
|
+
graph_dir = cwd / ".htmlgraph"
|
|
84
|
+
|
|
85
|
+
if not graph_dir.exists():
|
|
86
|
+
for parent in [cwd.parent, cwd.parent.parent, cwd.parent.parent.parent]:
|
|
87
|
+
candidate = parent / ".htmlgraph"
|
|
88
|
+
if candidate.exists():
|
|
89
|
+
graph_dir = candidate
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
return graph_dir
|
|
93
|
+
|
|
94
|
+
def enforce(self, tool: str, params: dict[str, Any]) -> dict[str, Any]:
|
|
95
|
+
"""
|
|
96
|
+
Enforce CIGS delegation rules with escalating guidance.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
tool: Tool name (Read, Edit, Bash, etc.)
|
|
100
|
+
params: Tool parameters
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Hook response dict in Claude Code standard format:
|
|
104
|
+
{
|
|
105
|
+
"hookSpecificOutput": {
|
|
106
|
+
"hookEventName": "PreToolUse",
|
|
107
|
+
"permissionDecision": "allow" | "deny",
|
|
108
|
+
"additionalContext": "...", # If allow with guidance
|
|
109
|
+
"permissionDecisionReason": "...", # If deny
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
"""
|
|
113
|
+
# Check if orchestrator mode is enabled
|
|
114
|
+
if not self.manager.is_enabled():
|
|
115
|
+
return self._allow()
|
|
116
|
+
|
|
117
|
+
enforcement_level = self.manager.get_enforcement_level()
|
|
118
|
+
|
|
119
|
+
# ALWAYS ALLOWED tools pass through
|
|
120
|
+
if tool in self.ALWAYS_ALLOWED:
|
|
121
|
+
return self._allow()
|
|
122
|
+
|
|
123
|
+
# Check if SDK operation (always allowed)
|
|
124
|
+
if self._is_sdk_operation(tool, params):
|
|
125
|
+
return self._allow()
|
|
126
|
+
|
|
127
|
+
# Get session violation summary
|
|
128
|
+
summary = self.tracker.get_session_violations()
|
|
129
|
+
violation_count = summary.total_violations
|
|
130
|
+
|
|
131
|
+
# Check circuit breaker (3+ violations)
|
|
132
|
+
if violation_count >= 3 and enforcement_level == "strict":
|
|
133
|
+
return self._circuit_breaker(violation_count)
|
|
134
|
+
|
|
135
|
+
# Classify operation using existing orchestrator logic
|
|
136
|
+
is_allowed, reason, category = is_allowed_orchestrator_operation(tool, params)
|
|
137
|
+
|
|
138
|
+
# CIGS enforces stricter rules in strict mode:
|
|
139
|
+
# - Even "single lookups" should be delegated (exploration tools)
|
|
140
|
+
# - All implementation tools should be delegated
|
|
141
|
+
should_delegate = False
|
|
142
|
+
if enforcement_level == "strict":
|
|
143
|
+
if tool in self.EXPLORATION_TOOLS or tool in self.IMPLEMENTATION_TOOLS:
|
|
144
|
+
should_delegate = True
|
|
145
|
+
# Override is_allowed - CIGS wants delegation even for first use
|
|
146
|
+
is_allowed = False
|
|
147
|
+
|
|
148
|
+
# If orchestrator allows and CIGS doesn't override, proceed
|
|
149
|
+
if is_allowed and not should_delegate:
|
|
150
|
+
return self._allow()
|
|
151
|
+
|
|
152
|
+
# Operation should be delegated - classify with cost analysis
|
|
153
|
+
classification = self.cost_calculator.classify_operation(
|
|
154
|
+
tool=tool,
|
|
155
|
+
params=params,
|
|
156
|
+
is_exploration_sequence=self._is_exploration_sequence(tool),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Generate imperative message with escalation
|
|
160
|
+
imperative_message = self.message_generator.generate(
|
|
161
|
+
tool=tool,
|
|
162
|
+
classification=classification,
|
|
163
|
+
violation_count=violation_count,
|
|
164
|
+
autonomy_level=enforcement_level,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Record violation for session tracking
|
|
168
|
+
predicted_waste = classification.predicted_cost - classification.optimal_cost
|
|
169
|
+
self.tracker.record_violation(
|
|
170
|
+
tool=tool,
|
|
171
|
+
params=params,
|
|
172
|
+
classification=classification,
|
|
173
|
+
predicted_waste=predicted_waste,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Return response based on enforcement level and escalation
|
|
177
|
+
if enforcement_level == "strict":
|
|
178
|
+
# STRICT mode - deny with imperative message
|
|
179
|
+
return {
|
|
180
|
+
"hookSpecificOutput": {
|
|
181
|
+
"hookEventName": "PreToolUse",
|
|
182
|
+
"permissionDecision": "deny",
|
|
183
|
+
"permissionDecisionReason": imperative_message,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else:
|
|
187
|
+
# GUIDANCE mode - allow but with strong message
|
|
188
|
+
return {
|
|
189
|
+
"hookSpecificOutput": {
|
|
190
|
+
"hookEventName": "PreToolUse",
|
|
191
|
+
"permissionDecision": "allow",
|
|
192
|
+
"additionalContext": imperative_message,
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
def _allow(self) -> dict[str, Any]:
|
|
197
|
+
"""Return allow response."""
|
|
198
|
+
return {
|
|
199
|
+
"hookSpecificOutput": {
|
|
200
|
+
"hookEventName": "PreToolUse",
|
|
201
|
+
"permissionDecision": "allow",
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
def _circuit_breaker(self, violation_count: int) -> dict[str, Any]:
|
|
206
|
+
"""Return circuit breaker blocking response."""
|
|
207
|
+
message = (
|
|
208
|
+
"🚨 CIRCUIT BREAKER TRIGGERED\n\n"
|
|
209
|
+
f"You have violated delegation rules {violation_count} times this session.\n\n"
|
|
210
|
+
"**Violations detected:**\n"
|
|
211
|
+
"- Direct execution instead of delegation\n"
|
|
212
|
+
"- Context waste on tactical operations\n"
|
|
213
|
+
"- Ignored imperative guidance messages\n\n"
|
|
214
|
+
"**REQUIRED:** Acknowledge violations before proceeding:\n"
|
|
215
|
+
"`uv run htmlgraph orchestrator acknowledge-violation`\n\n"
|
|
216
|
+
"**OR** Change enforcement settings:\n"
|
|
217
|
+
"- Disable: `uv run htmlgraph orchestrator disable`\n"
|
|
218
|
+
"- Guidance mode: `uv run htmlgraph orchestrator set-level guidance`\n"
|
|
219
|
+
"- Reset violations: `uv run htmlgraph orchestrator reset-violations`"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
"hookSpecificOutput": {
|
|
224
|
+
"hookEventName": "PreToolUse",
|
|
225
|
+
"permissionDecision": "deny",
|
|
226
|
+
"permissionDecisionReason": message,
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
def _is_sdk_operation(self, tool: str, params: dict[str, Any]) -> bool:
|
|
231
|
+
"""Check if operation is an SDK operation (always allowed)."""
|
|
232
|
+
if tool != "Bash":
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
command = params.get("command", "")
|
|
236
|
+
|
|
237
|
+
# Allow htmlgraph SDK commands
|
|
238
|
+
if command.startswith("uv run htmlgraph ") or command.startswith("htmlgraph "):
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
# Allow git read-only commands
|
|
242
|
+
if command.startswith(("git status", "git diff", "git log")):
|
|
243
|
+
return True
|
|
244
|
+
|
|
245
|
+
# Allow SDK inline usage
|
|
246
|
+
if "from htmlgraph import" in command or "import htmlgraph" in command:
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
def _is_exploration_sequence(self, tool: str) -> bool:
|
|
252
|
+
"""Check if this is part of an exploration sequence."""
|
|
253
|
+
if tool not in self.EXPLORATION_TOOLS:
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
# Check recent history for exploration pattern
|
|
257
|
+
# This is simplified - could use tool_history from orchestrator.py
|
|
258
|
+
summary = self.tracker.get_session_violations()
|
|
259
|
+
|
|
260
|
+
# If we've already had exploration violations, this is a sequence
|
|
261
|
+
exploration_violations = [
|
|
262
|
+
v for v in summary.violations if v.tool in self.EXPLORATION_TOOLS
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
return len(exploration_violations) >= 1
|
|
266
|
+
|
|
267
|
+
def _get_or_create_session_id(self) -> str:
|
|
268
|
+
"""Get or create a session ID for tracking."""
|
|
269
|
+
# Try to get from environment
|
|
270
|
+
if "HTMLGRAPH_SESSION_ID" in os.environ:
|
|
271
|
+
return os.environ["HTMLGRAPH_SESSION_ID"]
|
|
272
|
+
|
|
273
|
+
# Try to get from session manager
|
|
274
|
+
try:
|
|
275
|
+
from htmlgraph.session_manager import SessionManager
|
|
276
|
+
|
|
277
|
+
sm = SessionManager(self.graph_dir)
|
|
278
|
+
current = sm.get_active_session()
|
|
279
|
+
if current:
|
|
280
|
+
return str(current.id)
|
|
281
|
+
except Exception:
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
# Fallback: create a session ID for this test/run
|
|
285
|
+
# Use a consistent ID for the process
|
|
286
|
+
if not hasattr(self.__class__, "_fallback_session_id"):
|
|
287
|
+
from uuid import uuid4
|
|
288
|
+
|
|
289
|
+
fallback_id: str = f"test-session-{uuid4().hex[:8]}"
|
|
290
|
+
setattr(self.__class__, "_fallback_session_id", fallback_id)
|
|
291
|
+
return fallback_id
|
|
292
|
+
|
|
293
|
+
return str(getattr(self.__class__, "_fallback_session_id"))
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def enforce_cigs_pretool(tool_input: dict[str, Any]) -> dict[str, Any]:
|
|
297
|
+
"""
|
|
298
|
+
Main entry point for CIGS PreToolUse enforcement.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
tool_input: Hook input with tool name and parameters
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Hook response dict in Claude Code standard format
|
|
305
|
+
"""
|
|
306
|
+
# Extract tool and params from input
|
|
307
|
+
tool = tool_input.get("name", "") or tool_input.get("tool_name", "")
|
|
308
|
+
params = tool_input.get("input", {}) or tool_input.get("tool_input", {})
|
|
309
|
+
|
|
310
|
+
# Create enforcer and run
|
|
311
|
+
try:
|
|
312
|
+
enforcer = CIGSPreToolEnforcer()
|
|
313
|
+
return enforcer.enforce(tool, params)
|
|
314
|
+
except Exception as e:
|
|
315
|
+
# Graceful degradation - allow on error
|
|
316
|
+
logger.warning(f"Warning: CIGS enforcement error: {e}")
|
|
317
|
+
return {
|
|
318
|
+
"hookSpecificOutput": {
|
|
319
|
+
"hookEventName": "PreToolUse",
|
|
320
|
+
"permissionDecision": "allow",
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def main() -> None:
|
|
326
|
+
"""Hook entry point for script wrapper."""
|
|
327
|
+
# Check environment overrides
|
|
328
|
+
if os.environ.get("HTMLGRAPH_DISABLE_TRACKING") == "1":
|
|
329
|
+
print(json.dumps({"hookSpecificOutput": {"permissionDecision": "allow"}}))
|
|
330
|
+
sys.exit(0)
|
|
331
|
+
|
|
332
|
+
if os.environ.get("HTMLGRAPH_ORCHESTRATOR_DISABLED") == "1":
|
|
333
|
+
print(json.dumps({"hookSpecificOutput": {"permissionDecision": "allow"}}))
|
|
334
|
+
sys.exit(0)
|
|
335
|
+
|
|
336
|
+
# Read tool input from stdin
|
|
337
|
+
try:
|
|
338
|
+
tool_input = json.load(sys.stdin)
|
|
339
|
+
except json.JSONDecodeError:
|
|
340
|
+
tool_input = {}
|
|
341
|
+
|
|
342
|
+
# Run CIGS enforcement
|
|
343
|
+
result = enforce_cigs_pretool(tool_input)
|
|
344
|
+
|
|
345
|
+
# Output response
|
|
346
|
+
print(json.dumps(result))
|
|
347
|
+
|
|
348
|
+
# Exit code based on permission decision
|
|
349
|
+
permission = result.get("hookSpecificOutput", {}).get("permissionDecision", "allow")
|
|
350
|
+
sys.exit(0 if permission == "allow" else 1)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
if __name__ == "__main__":
|
|
354
|
+
main()
|