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.
Files changed (239) hide show
  1. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/PKG-INFO +1 -1
  2. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/gemini-extension.json +1 -1
  3. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/pyproject.toml +1 -1
  4. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/__init__.py +1 -1
  5. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/cli.py +66 -0
  6. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/orchestrator.py +47 -6
  7. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/orchestrator_mode.py +78 -0
  8. htmlgraph-0.20.3/tests/python/test_orchestrator_circuit_breaker.py +322 -0
  9. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/.gitignore +0 -0
  10. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/README.md +0 -0
  11. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/agent-coordination/README.md +0 -0
  12. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/agent-coordination/demo.py +0 -0
  13. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/create_auth_track_demo.py +0 -0
  14. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/create_htmlgraph_dev_track.py +0 -0
  15. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/README.md +0 -0
  16. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/agents.json +0 -0
  17. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/demo.py +0 -0
  18. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-api-graph.html +0 -0
  19. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-api-overview.html +0 -0
  20. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-api-sdk.html +0 -0
  21. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-example-advanced.html +0 -0
  22. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-example-basic.html +0 -0
  23. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-index.html +0 -0
  24. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-installation.html +0 -0
  25. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/documentation-site/features/doc-quickstart.html +0 -0
  26. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/handoff-demo.py +0 -0
  27. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/README.md +0 -0
  28. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/agents.json +0 -0
  29. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/demo.py +0 -0
  30. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/features/feat-276c615f.html +0 -0
  31. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/features/feat-7a6f797d.html +0 -0
  32. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/features/note-ai-agents.html +0 -0
  33. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/features/note-graph-db.html +0 -0
  34. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/features/note-htmlgraph.html +0 -0
  35. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/knowledge-base/features/note-web-standards.html +0 -0
  36. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/parallel_task_coordination.py +0 -0
  37. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/routing-demo.py +0 -0
  38. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/sdk_demo.py +0 -0
  39. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/README.md +0 -0
  40. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/demo.py +0 -0
  41. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/index.html +0 -0
  42. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/styles.css +0 -0
  43. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/task-001.html +0 -0
  44. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/task-002.html +0 -0
  45. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/task-003.html +0 -0
  46. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/task-004.html +0 -0
  47. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/task-005.html +0 -0
  48. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/examples/todo-list/task-006.html +0 -0
  49. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/GEMINI.md +0 -0
  50. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/README.md +0 -0
  51. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_deploy.md +0 -0
  52. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_end.md +0 -0
  53. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_feature-add.md +0 -0
  54. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_feature-complete.md +0 -0
  55. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_feature-primary.md +0 -0
  56. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_feature-start.md +0 -0
  57. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_git-commit.md +0 -0
  58. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_help.md +0 -0
  59. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_init.md +0 -0
  60. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_plan.md +0 -0
  61. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_recommend.md +0 -0
  62. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_serve.md +0 -0
  63. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_spike.md +0 -0
  64. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_start.md +0 -0
  65. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_status.md +0 -0
  66. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/command_track.md +0 -0
  67. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/hooks/hooks.json +0 -0
  68. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/hooks/scripts/post-tool.sh +0 -0
  69. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/hooks/scripts/session-end.sh +0 -0
  70. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/packages/gemini-extension/hooks/scripts/session-start.sh +0 -0
  71. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/js/htmlgraph.js +0 -0
  72. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/agent_detection.py +0 -0
  73. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/agent_registry.py +0 -0
  74. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/agents.py +0 -0
  75. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/analytics/__init__.py +0 -0
  76. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/analytics/cli.py +0 -0
  77. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/analytics/dependency.py +0 -0
  78. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/analytics/work_type.py +0 -0
  79. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/analytics_index.py +0 -0
  80. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/attribute_index.py +0 -0
  81. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/__init__.py +0 -0
  82. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/base.py +0 -0
  83. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/bug.py +0 -0
  84. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/chore.py +0 -0
  85. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/epic.py +0 -0
  86. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/feature.py +0 -0
  87. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/insight.py +0 -0
  88. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/metric.py +0 -0
  89. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/pattern.py +0 -0
  90. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/phase.py +0 -0
  91. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/spike.py +0 -0
  92. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/builders/track.py +0 -0
  93. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/__init__.py +0 -0
  94. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/base.py +0 -0
  95. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/bug.py +0 -0
  96. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/chore.py +0 -0
  97. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/epic.py +0 -0
  98. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/feature.py +0 -0
  99. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/insight.py +0 -0
  100. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/metric.py +0 -0
  101. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/pattern.py +0 -0
  102. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/phase.py +0 -0
  103. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/spike.py +0 -0
  104. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/collections/todo.py +0 -0
  105. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/context_analytics.py +0 -0
  106. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/converter.py +0 -0
  107. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/dashboard.html +0 -0
  108. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/dependency_models.py +0 -0
  109. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/deploy.py +0 -0
  110. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/deployment_models.py +0 -0
  111. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/edge_index.py +0 -0
  112. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/event_log.py +0 -0
  113. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/event_migration.py +0 -0
  114. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/exceptions.py +0 -0
  115. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/GEMINI.md +0 -0
  116. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/README.md +0 -0
  117. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/gemini-extension.json +0 -0
  118. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/hooks/hooks.json +0 -0
  119. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/hooks/scripts/post-tool.sh +0 -0
  120. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/hooks/scripts/session-end.sh +0 -0
  121. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/extensions/gemini/hooks/scripts/session-start.sh +0 -0
  122. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/file_watcher.py +0 -0
  123. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/find_api.py +0 -0
  124. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/git_events.py +0 -0
  125. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/graph.py +0 -0
  126. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/__init__.py +0 -0
  127. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/event_tracker.py +0 -0
  128. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/hooks-config.example.json +0 -0
  129. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/installer.py +0 -0
  130. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/orchestrator_reflector.py +0 -0
  131. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/post-checkout.sh +0 -0
  132. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/post-commit.sh +0 -0
  133. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/post-merge.sh +0 -0
  134. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/post_tool_use_failure.py +0 -0
  135. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/posttooluse.py +0 -0
  136. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/pre-commit.sh +0 -0
  137. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/pre-push.sh +0 -0
  138. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/pretooluse.py +0 -0
  139. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/task_enforcer.py +0 -0
  140. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/task_validator.py +0 -0
  141. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/hooks/validator.py +0 -0
  142. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/ids.py +0 -0
  143. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/index.d.ts +0 -0
  144. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/learning.py +0 -0
  145. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/mcp_server.py +0 -0
  146. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/models.py +0 -0
  147. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/orchestration.py +0 -0
  148. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/orchestrator.py +0 -0
  149. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/orchestrator_validator.py +0 -0
  150. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/parallel.py +0 -0
  151. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/parser.py +0 -0
  152. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/planning.py +0 -0
  153. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/query_builder.py +0 -0
  154. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/routing.py +0 -0
  155. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/scripts/__init__.py +0 -0
  156. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/scripts/deploy.py +0 -0
  157. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/sdk.py +0 -0
  158. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/server.py +0 -0
  159. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/services/__init__.py +0 -0
  160. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/services/claiming.py +0 -0
  161. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/session_manager.py +0 -0
  162. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/session_warning.py +0 -0
  163. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/setup.py +0 -0
  164. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/spike_index.py +0 -0
  165. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/styles.css +0 -0
  166. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/sync_docs.py +0 -0
  167. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/templates/AGENTS.md.template +0 -0
  168. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/templates/CLAUDE.md.template +0 -0
  169. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/templates/GEMINI.md.template +0 -0
  170. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/track_builder.py +0 -0
  171. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/track_manager.py +0 -0
  172. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/transcript.py +0 -0
  173. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/transcript_analytics.py +0 -0
  174. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/types.py +0 -0
  175. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/watch.py +0 -0
  176. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/src/python/htmlgraph/work_type_utils.py +0 -0
  177. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/benchmarks/README.md +0 -0
  178. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/benchmarks/__init__.py +0 -0
  179. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/benchmarks/bench_graph.py +0 -0
  180. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/benchmarks/conftest.py +0 -0
  181. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/integration/test_deployment_workflow.py +0 -0
  182. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/integration/test_handoff_routing.py +0 -0
  183. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/integration/test_multi_agent_coordination.py +0 -0
  184. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/js/README.md +0 -0
  185. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/js/test_htmlgraph.html +0 -0
  186. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/__init__.py +0 -0
  187. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_agent_detection.py +0 -0
  188. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_analytics.py +0 -0
  189. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_auto_spike.py +0 -0
  190. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_claiming.py +0 -0
  191. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_cli_commands.py +0 -0
  192. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_collection_filter.py +0 -0
  193. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_cross_agent_attribution.py +0 -0
  194. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_dashboard_ui.py +0 -0
  195. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_delete.py +0 -0
  196. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_deploy.py +0 -0
  197. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_drift_parent_activity.py +0 -0
  198. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_drift_queue_cleanup.py +0 -0
  199. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_edge_index.py +0 -0
  200. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_event_index.py +0 -0
  201. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_event_query.py +0 -0
  202. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_find_api.py +0 -0
  203. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_git_events.py +0 -0
  204. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_graph_traversal.py +0 -0
  205. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_handoff.py +0 -0
  206. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_ids.py +0 -0
  207. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_install_hooks.py +0 -0
  208. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_lifecycle.py +0 -0
  209. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_mcp_server.py +0 -0
  210. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_mcp_stdio_transport.py +0 -0
  211. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_models.py +0 -0
  212. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_orchestration.py +0 -0
  213. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_orchestrator_cli.py +0 -0
  214. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_orchestrator_enforce_hook.py +0 -0
  215. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_orchestrator_mode.py +0 -0
  216. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_post_tool_use_failure.py +0 -0
  217. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_pre_work_validation_integration.py +0 -0
  218. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_query_builder.py +0 -0
  219. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_query_cache.py +0 -0
  220. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_query_compilation.py +0 -0
  221. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_routing.py +0 -0
  222. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_sdk_discoverability.py +0 -0
  223. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_sdk_get_active_work_item.py +0 -0
  224. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_session_cleanup.py +0 -0
  225. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_session_continuity.py +0 -0
  226. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_session_start_info_validation.py +0 -0
  227. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_system_overhead_drift.py +0 -0
  228. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_todo_collection.py +0 -0
  229. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_track_loading.py +0 -0
  230. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_transcript_linking.py +0 -0
  231. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_update_preserves_incoming_edges.py +0 -0
  232. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_validate_work_hook.py +0 -0
  233. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_work_queue.py +0 -0
  234. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/python/test_work_type.py +0 -0
  235. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/test_agent_routing.py +0 -0
  236. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/test_deployment_models.py +0 -0
  237. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/test_orchestrator_validator.py +0 -0
  238. {htmlgraph-0.20.2 → htmlgraph-0.20.3}/tests/test_transaction_snapshot.py +0 -0
  239. {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.2
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.2",
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",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "htmlgraph"
3
- version = "0.20.2"
3
+ version = "0.20.3"
4
4
  description = "HTML is All You Need - Graph database on web standards"
5
5
  authors = [{name = "Shakes", email = "shakestzd@gmail.com"}]
6
6
  readme = "README.md"
@@ -84,7 +84,7 @@ from htmlgraph.types import (
84
84
  )
85
85
  from htmlgraph.work_type_utils import infer_work_type, infer_work_type_from_id
86
86
 
87
- __version__ = "0.20.2"
87
+ __version__ = "0.20.3"
88
88
  __all__ = [
89
89
  # Exceptions
90
90
  "HtmlGraphError",
@@ -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 strong warnings
390
- # NOTE: {"continue": False} doesn't work in Claude Code, so we use advisory warnings only
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 but allow (blocking doesn't work)
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
- f"See ORCHESTRATOR_DIRECTIVES in session context for HtmlGraph delegation pattern.\n"
401
- f"To disable orchestrator mode: uv run htmlgraph orchestrator disable"
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