htmlgraph 0.20.2__tar.gz → 0.20.3__tar.gz
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-0.20.2 → htmlgraph-0.20.3}/PKG-INFO +1 -1
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/gemini-extension.json +1 -1
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/pyproject.toml +1 -1
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/__init__.py +1 -1
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/cli.py +66 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/orchestrator.py +47 -6
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/orchestrator_mode.py +78 -0
- htmlgraph-0.20.3/tests/python/test_orchestrator_circuit_breaker.py +322 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/.gitignore +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/README.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/agent-coordination/README.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/agent-coordination/demo.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/create_auth_track_demo.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/create_htmlgraph_dev_track.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/README.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/agents.json +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/demo.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-api-graph.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-api-overview.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-api-sdk.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-example-advanced.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-example-basic.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-index.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-installation.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-quickstart.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/handoff-demo.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/README.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/agents.json +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/demo.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/features/feat-276c615f.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/features/feat-7a6f797d.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/features/note-ai-agents.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/features/note-graph-db.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/features/note-htmlgraph.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/features/note-web-standards.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/parallel_task_coordination.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/routing-demo.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/sdk_demo.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/README.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/demo.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/index.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/styles.css +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/task-001.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/task-002.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/task-003.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/task-004.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/task-005.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/task-006.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/GEMINI.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/README.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_deploy.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_end.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_feature-add.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_feature-complete.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_feature-primary.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_feature-start.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_git-commit.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_help.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_init.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_plan.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_recommend.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_serve.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_spike.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_start.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_status.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_track.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/hooks/hooks.json +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/hooks/scripts/post-tool.sh +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/hooks/scripts/session-end.sh +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/hooks/scripts/session-start.sh +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/js/htmlgraph.js +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/agent_detection.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/agent_registry.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/agents.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/analytics/__init__.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/analytics/cli.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/analytics/dependency.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/analytics/work_type.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/analytics_index.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/attribute_index.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/__init__.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/base.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/bug.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/chore.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/epic.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/feature.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/insight.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/metric.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/pattern.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/phase.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/spike.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/track.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/__init__.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/base.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/bug.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/chore.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/epic.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/feature.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/insight.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/metric.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/pattern.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/phase.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/spike.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/todo.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/context_analytics.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/converter.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/dashboard.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/dependency_models.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/deploy.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/deployment_models.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/edge_index.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/event_log.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/event_migration.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/exceptions.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/GEMINI.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/README.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/gemini-extension.json +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/hooks/hooks.json +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/hooks/scripts/post-tool.sh +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/hooks/scripts/session-end.sh +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/hooks/scripts/session-start.sh +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/file_watcher.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/find_api.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/git_events.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/graph.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/__init__.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/event_tracker.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/hooks-config.example.json +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/installer.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/orchestrator_reflector.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/post-checkout.sh +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/post-commit.sh +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/post-merge.sh +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/post_tool_use_failure.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/posttooluse.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/pre-commit.sh +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/pre-push.sh +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/pretooluse.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/task_enforcer.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/task_validator.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/validator.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/ids.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/index.d.ts +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/learning.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/mcp_server.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/models.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/orchestration.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/orchestrator.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/orchestrator_validator.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/parallel.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/parser.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/planning.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/query_builder.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/routing.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/scripts/__init__.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/scripts/deploy.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/sdk.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/server.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/services/__init__.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/services/claiming.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/session_manager.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/session_warning.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/setup.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/spike_index.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/sync_docs.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/track_builder.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/track_manager.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/transcript.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/transcript_analytics.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/types.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/watch.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/work_type_utils.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/benchmarks/README.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/benchmarks/__init__.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/benchmarks/bench_graph.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/benchmarks/conftest.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/integration/test_deployment_workflow.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/integration/test_handoff_routing.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/integration/test_multi_agent_coordination.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/js/README.md +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/js/test_htmlgraph.html +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/__init__.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_agent_detection.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_analytics.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_auto_spike.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_claiming.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_cli_commands.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_collection_filter.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_cross_agent_attribution.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_dashboard_ui.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_delete.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_deploy.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_drift_parent_activity.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_drift_queue_cleanup.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_edge_index.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_event_index.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_event_query.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_find_api.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_git_events.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_graph_traversal.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_handoff.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_ids.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_install_hooks.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_lifecycle.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_mcp_server.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_mcp_stdio_transport.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_models.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_orchestration.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_orchestrator_cli.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_orchestrator_enforce_hook.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_orchestrator_mode.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_post_tool_use_failure.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_pre_work_validation_integration.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_query_builder.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_query_cache.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_query_compilation.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_routing.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_sdk_discoverability.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_sdk_get_active_work_item.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_session_cleanup.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_session_continuity.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_session_start_info_validation.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_system_overhead_drift.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_todo_collection.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_track_loading.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_transcript_linking.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_update_preserves_incoming_edges.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_validate_work_hook.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_work_queue.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_work_type.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/test_agent_routing.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/test_deployment_models.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/test_orchestrator_validator.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/test_transaction_snapshot.py +0 -0
- {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/test_transcript.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: htmlgraph
|
|
3
|
-
Version: 0.20.
|
|
3
|
+
Version: 0.20.3
|
|
4
4
|
Summary: HTML is All You Need - Graph database on web standards
|
|
5
5
|
Project-URL: Homepage, https://github.com/Shakes-tzd/htmlgraph
|
|
6
6
|
Project-URL: Documentation, https://github.com/Shakes-tzd/htmlgraph#readme
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmlgraph",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.3",
|
|
4
4
|
"description": "HtmlGraph session tracking and documentation for Gemini CLI. Ensures proper activity attribution, feature awareness, and continuous work tracking.",
|
|
5
5
|
"author": "Shakes",
|
|
6
6
|
"license": "MIT",
|
|
@@ -2724,12 +2724,53 @@ def cmd_orchestrator_status(args: argparse.Namespace) -> None:
|
|
|
2724
2724
|
print(f"Activated at: {status['activated_at']}")
|
|
2725
2725
|
if status["auto_activated"]:
|
|
2726
2726
|
print("Auto-activated: yes")
|
|
2727
|
+
|
|
2728
|
+
# Show violation tracking info
|
|
2729
|
+
violations = status.get("violations", 0)
|
|
2730
|
+
circuit_breaker = status.get("circuit_breaker_triggered", False)
|
|
2731
|
+
if violations > 0:
|
|
2732
|
+
print(f"Violations: {violations}/3")
|
|
2733
|
+
if circuit_breaker:
|
|
2734
|
+
print("⚠️ Circuit breaker: TRIGGERED")
|
|
2727
2735
|
else:
|
|
2728
2736
|
print("Orchestrator mode: disabled")
|
|
2729
2737
|
if status["disabled_by_user"]:
|
|
2730
2738
|
print("Disabled by user (auto-activation prevented)")
|
|
2731
2739
|
|
|
2732
2740
|
|
|
2741
|
+
def cmd_orchestrator_set_level(args: argparse.Namespace) -> None:
|
|
2742
|
+
"""Set orchestrator mode enforcement level."""
|
|
2743
|
+
from typing import Literal
|
|
2744
|
+
|
|
2745
|
+
from htmlgraph.orchestrator_mode import OrchestratorModeManager
|
|
2746
|
+
|
|
2747
|
+
manager = OrchestratorModeManager(args.graph_dir)
|
|
2748
|
+
level: Literal["strict", "guidance"] = args.level
|
|
2749
|
+
manager.set_level(level)
|
|
2750
|
+
|
|
2751
|
+
level_text = "strict enforcement" if level == "strict" else "guidance mode"
|
|
2752
|
+
print(f"✓ Orchestrator enforcement level set to: {level_text}")
|
|
2753
|
+
|
|
2754
|
+
|
|
2755
|
+
def cmd_orchestrator_reset_violations(args: argparse.Namespace) -> None:
|
|
2756
|
+
"""Reset orchestrator mode violation counter."""
|
|
2757
|
+
from htmlgraph.orchestrator_mode import OrchestratorModeManager
|
|
2758
|
+
|
|
2759
|
+
manager = OrchestratorModeManager(args.graph_dir)
|
|
2760
|
+
|
|
2761
|
+
# Check if mode is enabled
|
|
2762
|
+
if not manager.is_enabled():
|
|
2763
|
+
print("⚠️ Orchestrator mode is not enabled")
|
|
2764
|
+
return
|
|
2765
|
+
|
|
2766
|
+
# Reset violations
|
|
2767
|
+
manager.reset_violations()
|
|
2768
|
+
|
|
2769
|
+
print("✓ Violation counter reset")
|
|
2770
|
+
print("Circuit breaker: cleared")
|
|
2771
|
+
print("You can now continue with delegation workflow")
|
|
2772
|
+
|
|
2773
|
+
|
|
2733
2774
|
def cmd_publish(args: argparse.Namespace) -> None:
|
|
2734
2775
|
"""Build and publish the package to PyPI (Interoperable)."""
|
|
2735
2776
|
import shutil
|
|
@@ -4475,6 +4516,27 @@ For more help: https://github.com/Shakes-tzd/htmlgraph
|
|
|
4475
4516
|
"--graph-dir", "-g", default=".htmlgraph", help="Graph directory"
|
|
4476
4517
|
)
|
|
4477
4518
|
|
|
4519
|
+
# orchestrator set-level
|
|
4520
|
+
orchestrator_set_level = orchestrator_subparsers.add_parser(
|
|
4521
|
+
"set-level", help="Set enforcement level"
|
|
4522
|
+
)
|
|
4523
|
+
orchestrator_set_level.add_argument(
|
|
4524
|
+
"level",
|
|
4525
|
+
choices=["strict", "guidance"],
|
|
4526
|
+
help="Enforcement level to set",
|
|
4527
|
+
)
|
|
4528
|
+
orchestrator_set_level.add_argument(
|
|
4529
|
+
"--graph-dir", "-g", default=".htmlgraph", help="Graph directory"
|
|
4530
|
+
)
|
|
4531
|
+
|
|
4532
|
+
# orchestrator reset-violations
|
|
4533
|
+
orchestrator_reset_violations = orchestrator_subparsers.add_parser(
|
|
4534
|
+
"reset-violations", help="Reset violation counter and circuit breaker"
|
|
4535
|
+
)
|
|
4536
|
+
orchestrator_reset_violations.add_argument(
|
|
4537
|
+
"--graph-dir", "-g", default=".htmlgraph", help="Graph directory"
|
|
4538
|
+
)
|
|
4539
|
+
|
|
4478
4540
|
# install-gemini-extension
|
|
4479
4541
|
subparsers.add_parser(
|
|
4480
4542
|
"install-gemini-extension",
|
|
@@ -4678,6 +4740,10 @@ For more help: https://github.com/Shakes-tzd/htmlgraph
|
|
|
4678
4740
|
cmd_orchestrator_disable(args)
|
|
4679
4741
|
elif args.orchestrator_command == "status":
|
|
4680
4742
|
cmd_orchestrator_status(args)
|
|
4743
|
+
elif args.orchestrator_command == "set-level":
|
|
4744
|
+
cmd_orchestrator_set_level(args)
|
|
4745
|
+
elif args.orchestrator_command == "reset-violations":
|
|
4746
|
+
cmd_orchestrator_reset_violations(args)
|
|
4681
4747
|
else:
|
|
4682
4748
|
orchestrator_parser.print_help()
|
|
4683
4749
|
sys.exit(1)
|
|
@@ -358,6 +358,31 @@ def enforce_orchestrator_mode(tool: str, params: dict) -> dict:
|
|
|
358
358
|
},
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
+
# Check if circuit breaker is triggered in strict mode
|
|
362
|
+
if enforcement_level == "strict" and manager.is_circuit_breaker_triggered():
|
|
363
|
+
# Circuit breaker triggered - block all non-core operations
|
|
364
|
+
if tool not in ["Task", "AskUserQuestion", "TodoWrite"]:
|
|
365
|
+
circuit_breaker_message = (
|
|
366
|
+
"🚨 ORCHESTRATOR CIRCUIT BREAKER TRIGGERED\n\n"
|
|
367
|
+
f"You have violated delegation rules {manager.get_violation_count()} times this session.\n\n"
|
|
368
|
+
"Violations detected:\n"
|
|
369
|
+
"- Direct execution instead of delegation\n"
|
|
370
|
+
"- Context waste on tactical operations\n\n"
|
|
371
|
+
"Options:\n"
|
|
372
|
+
"1. Disable orchestrator mode: uv run htmlgraph orchestrator disable\n"
|
|
373
|
+
"2. Change to guidance mode: uv run htmlgraph orchestrator set-level guidance\n"
|
|
374
|
+
"3. Reset counter (acknowledge violations): uv run htmlgraph orchestrator reset-violations\n\n"
|
|
375
|
+
"To proceed, choose an option above."
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
"hookSpecificOutput": {
|
|
380
|
+
"hookEventName": "PreToolUse",
|
|
381
|
+
"permissionDecision": "deny",
|
|
382
|
+
"permissionDecisionReason": circuit_breaker_message,
|
|
383
|
+
},
|
|
384
|
+
}
|
|
385
|
+
|
|
361
386
|
# Check if operation is allowed
|
|
362
387
|
is_allowed, reason, category = is_allowed_orchestrator_operation(tool, params)
|
|
363
388
|
|
|
@@ -386,19 +411,35 @@ def enforce_orchestrator_mode(tool: str, params: dict) -> dict:
|
|
|
386
411
|
},
|
|
387
412
|
}
|
|
388
413
|
|
|
389
|
-
# Operation not allowed - provide
|
|
390
|
-
|
|
414
|
+
# Operation not allowed - track violation and provide warnings
|
|
415
|
+
if enforcement_level == "strict":
|
|
416
|
+
# Increment violation counter
|
|
417
|
+
mode = manager.increment_violation()
|
|
418
|
+
violations = mode.violations
|
|
419
|
+
|
|
391
420
|
suggestion = create_task_suggestion(tool, params)
|
|
392
421
|
|
|
393
422
|
if enforcement_level == "strict":
|
|
394
|
-
# STRICT mode - loud warning
|
|
423
|
+
# STRICT mode - loud warning with violation count
|
|
395
424
|
error_message = (
|
|
396
|
-
f"🚫 ORCHESTRATOR MODE VIOLATION: {reason}\n\n"
|
|
425
|
+
f"🚫 ORCHESTRATOR MODE VIOLATION ({violations}/3): {reason}\n\n"
|
|
397
426
|
f"⚠️ WARNING: Direct operations waste context and break delegation pattern!\n\n"
|
|
398
427
|
f"Suggested delegation:\n"
|
|
399
428
|
f"{suggestion}\n\n"
|
|
400
|
-
|
|
401
|
-
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# Add circuit breaker warning if approaching threshold
|
|
432
|
+
if violations >= 3:
|
|
433
|
+
error_message += (
|
|
434
|
+
"🚨 CIRCUIT BREAKER TRIGGERED - Further violations will be blocked!\n\n"
|
|
435
|
+
"Reset with: uv run htmlgraph orchestrator reset-violations\n"
|
|
436
|
+
)
|
|
437
|
+
elif violations == 2:
|
|
438
|
+
error_message += "⚠️ Next violation will trigger circuit breaker!\n\n"
|
|
439
|
+
|
|
440
|
+
error_message += (
|
|
441
|
+
"See ORCHESTRATOR_DIRECTIVES in session context for HtmlGraph delegation pattern.\n"
|
|
442
|
+
"To disable orchestrator mode: uv run htmlgraph orchestrator disable"
|
|
402
443
|
)
|
|
403
444
|
|
|
404
445
|
return {
|
|
@@ -34,6 +34,15 @@ class OrchestratorMode(BaseModel):
|
|
|
34
34
|
disabled_by_user: bool = False
|
|
35
35
|
"""Whether user explicitly disabled mode (prevents auto-reactivation)."""
|
|
36
36
|
|
|
37
|
+
violations: int = 0
|
|
38
|
+
"""Count of delegation violations in current session."""
|
|
39
|
+
|
|
40
|
+
last_violation_at: datetime | None = None
|
|
41
|
+
"""Timestamp of most recent violation."""
|
|
42
|
+
|
|
43
|
+
circuit_breaker_triggered: bool = False
|
|
44
|
+
"""Whether circuit breaker has been triggered (3+ violations)."""
|
|
45
|
+
|
|
37
46
|
def to_dict(self) -> dict:
|
|
38
47
|
"""Convert to dict for JSON serialization."""
|
|
39
48
|
return {
|
|
@@ -45,6 +54,11 @@ class OrchestratorMode(BaseModel):
|
|
|
45
54
|
"enforcement_level": self.enforcement_level,
|
|
46
55
|
"auto_activated": self.auto_activated,
|
|
47
56
|
"disabled_by_user": self.disabled_by_user,
|
|
57
|
+
"violations": self.violations,
|
|
58
|
+
"last_violation_at": (
|
|
59
|
+
self.last_violation_at.isoformat() if self.last_violation_at else None
|
|
60
|
+
),
|
|
61
|
+
"circuit_breaker_triggered": self.circuit_breaker_triggered,
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
@classmethod
|
|
@@ -57,6 +71,13 @@ class OrchestratorMode(BaseModel):
|
|
|
57
71
|
activated_at = activated_at[:-1] + "+00:00"
|
|
58
72
|
activated_at = datetime.fromisoformat(activated_at)
|
|
59
73
|
|
|
74
|
+
last_violation_at = data.get("last_violation_at")
|
|
75
|
+
if last_violation_at:
|
|
76
|
+
# Handle both 'Z' suffix and '+00:00' timezone format
|
|
77
|
+
if last_violation_at.endswith("Z"):
|
|
78
|
+
last_violation_at = last_violation_at[:-1] + "+00:00"
|
|
79
|
+
last_violation_at = datetime.fromisoformat(last_violation_at)
|
|
80
|
+
|
|
60
81
|
return cls(
|
|
61
82
|
enabled=data.get("enabled", False),
|
|
62
83
|
activated_at=activated_at,
|
|
@@ -64,6 +85,9 @@ class OrchestratorMode(BaseModel):
|
|
|
64
85
|
enforcement_level=data.get("enforcement_level", "strict"),
|
|
65
86
|
auto_activated=data.get("auto_activated", False),
|
|
66
87
|
disabled_by_user=data.get("disabled_by_user", False),
|
|
88
|
+
violations=data.get("violations", 0),
|
|
89
|
+
last_violation_at=last_violation_at,
|
|
90
|
+
circuit_breaker_triggered=data.get("circuit_breaker_triggered", False),
|
|
67
91
|
)
|
|
68
92
|
|
|
69
93
|
|
|
@@ -214,4 +238,58 @@ class OrchestratorModeManager:
|
|
|
214
238
|
),
|
|
215
239
|
"auto_activated": mode.auto_activated,
|
|
216
240
|
"disabled_by_user": mode.disabled_by_user,
|
|
241
|
+
"violations": mode.violations,
|
|
242
|
+
"circuit_breaker_triggered": mode.circuit_breaker_triggered,
|
|
217
243
|
}
|
|
244
|
+
|
|
245
|
+
def increment_violation(self) -> OrchestratorMode:
|
|
246
|
+
"""
|
|
247
|
+
Increment violation counter and update timestamp.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Updated OrchestratorMode with incremented violations
|
|
251
|
+
"""
|
|
252
|
+
mode = self.load()
|
|
253
|
+
mode.violations += 1
|
|
254
|
+
mode.last_violation_at = datetime.now(timezone.utc)
|
|
255
|
+
|
|
256
|
+
# Trigger circuit breaker if threshold reached
|
|
257
|
+
if mode.violations >= 3:
|
|
258
|
+
mode.circuit_breaker_triggered = True
|
|
259
|
+
|
|
260
|
+
self.save(mode)
|
|
261
|
+
return mode
|
|
262
|
+
|
|
263
|
+
def reset_violations(self) -> OrchestratorMode:
|
|
264
|
+
"""
|
|
265
|
+
Reset violation counter and circuit breaker.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Updated OrchestratorMode with reset violations
|
|
269
|
+
"""
|
|
270
|
+
mode = self.load()
|
|
271
|
+
mode.violations = 0
|
|
272
|
+
mode.last_violation_at = None
|
|
273
|
+
mode.circuit_breaker_triggered = False
|
|
274
|
+
self.save(mode)
|
|
275
|
+
return mode
|
|
276
|
+
|
|
277
|
+
def is_circuit_breaker_triggered(self) -> bool:
|
|
278
|
+
"""
|
|
279
|
+
Check if circuit breaker is currently triggered.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
True if circuit breaker is active
|
|
283
|
+
"""
|
|
284
|
+
mode = self.load()
|
|
285
|
+
return mode.circuit_breaker_triggered
|
|
286
|
+
|
|
287
|
+
def get_violation_count(self) -> int:
|
|
288
|
+
"""
|
|
289
|
+
Get current violation count.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Number of violations in current session
|
|
293
|
+
"""
|
|
294
|
+
mode = self.load()
|
|
295
|
+
return mode.violations
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""Tests for orchestrator circuit breaker pattern."""
|
|
2
|
+
|
|
3
|
+
from htmlgraph.hooks.orchestrator import enforce_orchestrator_mode
|
|
4
|
+
from htmlgraph.orchestrator_mode import OrchestratorModeManager
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestCircuitBreaker:
|
|
8
|
+
"""Test circuit breaker enforcement."""
|
|
9
|
+
|
|
10
|
+
def test_violation_tracking_increments(self, tmp_path, monkeypatch):
|
|
11
|
+
"""Test that violations are tracked correctly."""
|
|
12
|
+
# Change to tmp_path so enforce_orchestrator_mode finds .htmlgraph
|
|
13
|
+
monkeypatch.chdir(tmp_path)
|
|
14
|
+
|
|
15
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
16
|
+
manager.enable(level="strict")
|
|
17
|
+
|
|
18
|
+
# Simulate blocked operation
|
|
19
|
+
result = enforce_orchestrator_mode("Edit", {"file_path": "test.py"})
|
|
20
|
+
|
|
21
|
+
# Check violation was recorded
|
|
22
|
+
assert manager.get_violation_count() == 1
|
|
23
|
+
assert not manager.is_circuit_breaker_triggered()
|
|
24
|
+
|
|
25
|
+
# Verify warning message includes violation count
|
|
26
|
+
assert (
|
|
27
|
+
"VIOLATION (1/3)"
|
|
28
|
+
in result["hookSpecificOutput"]["permissionDecisionReason"]
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def test_circuit_breaker_triggers_at_threshold(self, tmp_path, monkeypatch):
|
|
32
|
+
"""Test circuit breaker triggers at 3 violations."""
|
|
33
|
+
monkeypatch.chdir(tmp_path)
|
|
34
|
+
|
|
35
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
36
|
+
manager.enable(level="strict")
|
|
37
|
+
|
|
38
|
+
# Trigger 3 violations
|
|
39
|
+
for i in range(3):
|
|
40
|
+
enforce_orchestrator_mode("Edit", {"file_path": f"test{i}.py"})
|
|
41
|
+
|
|
42
|
+
# Circuit breaker should be triggered
|
|
43
|
+
assert manager.get_violation_count() == 3
|
|
44
|
+
assert manager.is_circuit_breaker_triggered()
|
|
45
|
+
|
|
46
|
+
def test_circuit_breaker_blocks_subsequent_operations(self, tmp_path, monkeypatch):
|
|
47
|
+
"""Test that circuit breaker blocks operations after trigger."""
|
|
48
|
+
monkeypatch.chdir(tmp_path)
|
|
49
|
+
|
|
50
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
51
|
+
manager.enable(level="strict")
|
|
52
|
+
|
|
53
|
+
# Trigger 3 violations
|
|
54
|
+
for i in range(3):
|
|
55
|
+
enforce_orchestrator_mode("Edit", {"file_path": f"test{i}.py"})
|
|
56
|
+
|
|
57
|
+
# Next operation should be blocked by circuit breaker
|
|
58
|
+
result = enforce_orchestrator_mode("Read", {"file_path": "test.py"})
|
|
59
|
+
|
|
60
|
+
assert result["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
61
|
+
assert (
|
|
62
|
+
"CIRCUIT BREAKER TRIGGERED"
|
|
63
|
+
in result["hookSpecificOutput"]["permissionDecisionReason"]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def test_circuit_breaker_allows_core_operations(self, tmp_path, monkeypatch):
|
|
67
|
+
"""Test that circuit breaker allows Task/AskUserQuestion/TodoWrite."""
|
|
68
|
+
monkeypatch.chdir(tmp_path)
|
|
69
|
+
|
|
70
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
71
|
+
manager.enable(level="strict")
|
|
72
|
+
|
|
73
|
+
# Trigger circuit breaker
|
|
74
|
+
for i in range(3):
|
|
75
|
+
enforce_orchestrator_mode("Edit", {"file_path": f"test{i}.py"})
|
|
76
|
+
|
|
77
|
+
# Core operations should still be allowed
|
|
78
|
+
core_ops = [
|
|
79
|
+
("Task", {"prompt": "test", "subagent_type": "general-purpose"}),
|
|
80
|
+
("AskUserQuestion", {"question": "test"}),
|
|
81
|
+
("TodoWrite", {"todos": []}),
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
for tool, params in core_ops:
|
|
85
|
+
result = enforce_orchestrator_mode(tool, params)
|
|
86
|
+
assert result["hookSpecificOutput"]["permissionDecision"] == "allow"
|
|
87
|
+
|
|
88
|
+
def test_reset_violations_clears_counter(self, tmp_path, monkeypatch):
|
|
89
|
+
"""Test that reset_violations clears counter and circuit breaker."""
|
|
90
|
+
monkeypatch.chdir(tmp_path)
|
|
91
|
+
|
|
92
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
93
|
+
manager.enable(level="strict")
|
|
94
|
+
|
|
95
|
+
# Trigger violations
|
|
96
|
+
for i in range(3):
|
|
97
|
+
enforce_orchestrator_mode("Edit", {"file_path": f"test{i}.py"})
|
|
98
|
+
|
|
99
|
+
assert manager.get_violation_count() == 3
|
|
100
|
+
assert manager.is_circuit_breaker_triggered()
|
|
101
|
+
|
|
102
|
+
# Reset
|
|
103
|
+
manager.reset_violations()
|
|
104
|
+
|
|
105
|
+
assert manager.get_violation_count() == 0
|
|
106
|
+
assert not manager.is_circuit_breaker_triggered()
|
|
107
|
+
|
|
108
|
+
def test_violation_warning_at_two(self, tmp_path, monkeypatch):
|
|
109
|
+
"""Test special warning at 2 violations."""
|
|
110
|
+
monkeypatch.chdir(tmp_path)
|
|
111
|
+
|
|
112
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
113
|
+
manager.enable(level="strict")
|
|
114
|
+
|
|
115
|
+
# First violation
|
|
116
|
+
enforce_orchestrator_mode("Edit", {"file_path": "test1.py"})
|
|
117
|
+
|
|
118
|
+
# Second violation should warn about next one
|
|
119
|
+
result = enforce_orchestrator_mode("Edit", {"file_path": "test2.py"})
|
|
120
|
+
|
|
121
|
+
assert (
|
|
122
|
+
"VIOLATION (2/3)"
|
|
123
|
+
in result["hookSpecificOutput"]["permissionDecisionReason"]
|
|
124
|
+
)
|
|
125
|
+
assert (
|
|
126
|
+
"Next violation will trigger circuit breaker"
|
|
127
|
+
in result["hookSpecificOutput"]["permissionDecisionReason"]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def test_violation_message_at_threshold(self, tmp_path, monkeypatch):
|
|
131
|
+
"""Test message when circuit breaker triggers."""
|
|
132
|
+
monkeypatch.chdir(tmp_path)
|
|
133
|
+
|
|
134
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
135
|
+
manager.enable(level="strict")
|
|
136
|
+
|
|
137
|
+
# First two violations
|
|
138
|
+
enforce_orchestrator_mode("Edit", {"file_path": "test1.py"})
|
|
139
|
+
enforce_orchestrator_mode("Edit", {"file_path": "test2.py"})
|
|
140
|
+
|
|
141
|
+
# Third violation triggers circuit breaker
|
|
142
|
+
result = enforce_orchestrator_mode("Edit", {"file_path": "test3.py"})
|
|
143
|
+
|
|
144
|
+
assert (
|
|
145
|
+
"VIOLATION (3/3)"
|
|
146
|
+
in result["hookSpecificOutput"]["permissionDecisionReason"]
|
|
147
|
+
)
|
|
148
|
+
assert (
|
|
149
|
+
"CIRCUIT BREAKER TRIGGERED"
|
|
150
|
+
in result["hookSpecificOutput"]["permissionDecisionReason"]
|
|
151
|
+
)
|
|
152
|
+
assert (
|
|
153
|
+
"reset-violations"
|
|
154
|
+
in result["hookSpecificOutput"]["permissionDecisionReason"]
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def test_guidance_mode_does_not_track_violations(self, tmp_path, monkeypatch):
|
|
158
|
+
"""Test that guidance mode doesn't track violations."""
|
|
159
|
+
monkeypatch.chdir(tmp_path)
|
|
160
|
+
|
|
161
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
162
|
+
manager.enable(level="guidance")
|
|
163
|
+
|
|
164
|
+
# Simulate multiple blocked operations
|
|
165
|
+
for i in range(5):
|
|
166
|
+
enforce_orchestrator_mode("Edit", {"file_path": f"test{i}.py"})
|
|
167
|
+
|
|
168
|
+
# No violations should be tracked in guidance mode
|
|
169
|
+
assert manager.get_violation_count() == 0
|
|
170
|
+
assert not manager.is_circuit_breaker_triggered()
|
|
171
|
+
|
|
172
|
+
def test_allowed_operations_dont_increment_violations(self, tmp_path, monkeypatch):
|
|
173
|
+
"""Test that allowed operations don't increment violation counter."""
|
|
174
|
+
monkeypatch.chdir(tmp_path)
|
|
175
|
+
|
|
176
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
177
|
+
manager.enable(level="strict")
|
|
178
|
+
|
|
179
|
+
# Allowed operations
|
|
180
|
+
enforce_orchestrator_mode(
|
|
181
|
+
"Task", {"prompt": "test", "subagent_type": "general-purpose"}
|
|
182
|
+
)
|
|
183
|
+
enforce_orchestrator_mode(
|
|
184
|
+
"Read", {"file_path": "test.py"}
|
|
185
|
+
) # First read is allowed
|
|
186
|
+
enforce_orchestrator_mode("Bash", {"command": "uv run htmlgraph status"})
|
|
187
|
+
|
|
188
|
+
# No violations should be recorded
|
|
189
|
+
assert manager.get_violation_count() == 0
|
|
190
|
+
|
|
191
|
+
def test_circuit_breaker_message_shows_options(self, tmp_path, monkeypatch):
|
|
192
|
+
"""Test that circuit breaker message shows all options."""
|
|
193
|
+
monkeypatch.chdir(tmp_path)
|
|
194
|
+
|
|
195
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
196
|
+
manager.enable(level="strict")
|
|
197
|
+
|
|
198
|
+
# Trigger circuit breaker
|
|
199
|
+
for i in range(3):
|
|
200
|
+
enforce_orchestrator_mode("Edit", {"file_path": f"test{i}.py"})
|
|
201
|
+
|
|
202
|
+
# Check subsequent operation shows options
|
|
203
|
+
result = enforce_orchestrator_mode("Read", {"file_path": "test.py"})
|
|
204
|
+
message = result["hookSpecificOutput"]["permissionDecisionReason"]
|
|
205
|
+
|
|
206
|
+
assert "disable" in message.lower()
|
|
207
|
+
assert "set-level guidance" in message.lower()
|
|
208
|
+
assert "reset-violations" in message.lower()
|
|
209
|
+
|
|
210
|
+
def test_status_shows_violation_count(self, tmp_path, monkeypatch):
|
|
211
|
+
"""Test that status command shows violation tracking."""
|
|
212
|
+
monkeypatch.chdir(tmp_path)
|
|
213
|
+
|
|
214
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
215
|
+
manager.enable(level="strict")
|
|
216
|
+
|
|
217
|
+
# Add some violations
|
|
218
|
+
enforce_orchestrator_mode("Edit", {"file_path": "test1.py"})
|
|
219
|
+
enforce_orchestrator_mode("Edit", {"file_path": "test2.py"})
|
|
220
|
+
|
|
221
|
+
status = manager.status()
|
|
222
|
+
|
|
223
|
+
assert status["violations"] == 2
|
|
224
|
+
assert status["circuit_breaker_triggered"] is False
|
|
225
|
+
|
|
226
|
+
def test_enable_resets_violations(self, tmp_path, monkeypatch):
|
|
227
|
+
"""Test that enabling orchestrator mode resets violations."""
|
|
228
|
+
monkeypatch.chdir(tmp_path)
|
|
229
|
+
|
|
230
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
231
|
+
manager.enable(level="strict")
|
|
232
|
+
|
|
233
|
+
# Trigger violations
|
|
234
|
+
for i in range(3):
|
|
235
|
+
enforce_orchestrator_mode("Edit", {"file_path": f"test{i}.py"})
|
|
236
|
+
|
|
237
|
+
assert manager.get_violation_count() == 3
|
|
238
|
+
|
|
239
|
+
# Re-enable should NOT reset (violations are session-specific)
|
|
240
|
+
# This test documents current behavior - violations persist across enable calls
|
|
241
|
+
manager.enable(level="strict")
|
|
242
|
+
assert manager.get_violation_count() == 3 # Violations persist
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class TestCircuitBreakerCLI:
|
|
246
|
+
"""Test CLI commands for circuit breaker."""
|
|
247
|
+
|
|
248
|
+
def test_reset_violations_command_requires_enabled_mode(
|
|
249
|
+
self, tmp_path, monkeypatch, capsys
|
|
250
|
+
):
|
|
251
|
+
"""Test reset-violations fails gracefully when mode disabled."""
|
|
252
|
+
from argparse import Namespace
|
|
253
|
+
|
|
254
|
+
from htmlgraph.cli import cmd_orchestrator_reset_violations
|
|
255
|
+
|
|
256
|
+
# Don't enable mode - just test CLI error handling
|
|
257
|
+
args = Namespace(graph_dir=tmp_path / ".htmlgraph")
|
|
258
|
+
cmd_orchestrator_reset_violations(args)
|
|
259
|
+
|
|
260
|
+
captured = capsys.readouterr()
|
|
261
|
+
assert "not enabled" in captured.out.lower()
|
|
262
|
+
|
|
263
|
+
def test_reset_violations_command_success(self, tmp_path, monkeypatch, capsys):
|
|
264
|
+
"""Test reset-violations command works."""
|
|
265
|
+
from argparse import Namespace
|
|
266
|
+
|
|
267
|
+
from htmlgraph.cli import cmd_orchestrator_reset_violations
|
|
268
|
+
|
|
269
|
+
monkeypatch.chdir(tmp_path)
|
|
270
|
+
|
|
271
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
272
|
+
manager.enable(level="strict")
|
|
273
|
+
|
|
274
|
+
# Add violations
|
|
275
|
+
for i in range(3):
|
|
276
|
+
enforce_orchestrator_mode("Edit", {"file_path": f"test{i}.py"})
|
|
277
|
+
|
|
278
|
+
# Reset via CLI
|
|
279
|
+
args = Namespace(graph_dir=tmp_path / ".htmlgraph")
|
|
280
|
+
cmd_orchestrator_reset_violations(args)
|
|
281
|
+
|
|
282
|
+
captured = capsys.readouterr()
|
|
283
|
+
assert "reset" in captured.out.lower()
|
|
284
|
+
assert manager.get_violation_count() == 0
|
|
285
|
+
|
|
286
|
+
def test_set_level_command(self, tmp_path, capsys):
|
|
287
|
+
"""Test set-level command works."""
|
|
288
|
+
from argparse import Namespace
|
|
289
|
+
|
|
290
|
+
from htmlgraph.cli import cmd_orchestrator_set_level
|
|
291
|
+
|
|
292
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
293
|
+
manager.enable(level="strict")
|
|
294
|
+
|
|
295
|
+
# Change to guidance
|
|
296
|
+
args = Namespace(graph_dir=tmp_path / ".htmlgraph", level="guidance")
|
|
297
|
+
cmd_orchestrator_set_level(args)
|
|
298
|
+
|
|
299
|
+
captured = capsys.readouterr()
|
|
300
|
+
assert "guidance" in captured.out.lower()
|
|
301
|
+
assert manager.get_enforcement_level() == "guidance"
|
|
302
|
+
|
|
303
|
+
def test_status_shows_violations(self, tmp_path, monkeypatch, capsys):
|
|
304
|
+
"""Test status command shows violation info."""
|
|
305
|
+
from argparse import Namespace
|
|
306
|
+
|
|
307
|
+
from htmlgraph.cli import cmd_orchestrator_status
|
|
308
|
+
|
|
309
|
+
monkeypatch.chdir(tmp_path)
|
|
310
|
+
|
|
311
|
+
manager = OrchestratorModeManager(tmp_path / ".htmlgraph")
|
|
312
|
+
manager.enable(level="strict")
|
|
313
|
+
|
|
314
|
+
# Add violations
|
|
315
|
+
enforce_orchestrator_mode("Edit", {"file_path": "test1.py"})
|
|
316
|
+
enforce_orchestrator_mode("Edit", {"file_path": "test2.py"})
|
|
317
|
+
|
|
318
|
+
args = Namespace(graph_dir=tmp_path / ".htmlgraph")
|
|
319
|
+
cmd_orchestrator_status(args)
|
|
320
|
+
|
|
321
|
+
captured = capsys.readouterr()
|
|
322
|
+
assert "2/3" in captured.out
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-api-graph.html
RENAMED
|
File without changes
|
{htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-api-overview.html
RENAMED
|
File without changes
|
|
File without changes
|
{htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-example-advanced.html
RENAMED
|
File without changes
|
{htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-example-basic.html
RENAMED
|
File without changes
|
|
File without changes
|
{htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-installation.html
RENAMED
|
File without changes
|