code-context-control 2.39.0__tar.gz → 2.39.1__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 (223) hide show
  1. {code_context_control-2.39.0/code_context_control.egg-info → code_context_control-2.39.1}/PKG-INFO +1 -1
  2. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/delegate.py +80 -2
  3. {code_context_control-2.39.0 → code_context_control-2.39.1/code_context_control.egg-info}/PKG-INFO +1 -1
  4. {code_context_control-2.39.0 → code_context_control-2.39.1}/code_context_control.egg-info/SOURCES.txt +2 -0
  5. {code_context_control-2.39.0 → code_context_control-2.39.1}/pyproject.toml +1 -1
  6. code_context_control-2.39.1/services/circuit_breaker.py +86 -0
  7. code_context_control-2.39.1/tests/test_circuit_breaker.py +103 -0
  8. {code_context_control-2.39.0 → code_context_control-2.39.1}/LICENSE +0 -0
  9. {code_context_control-2.39.0 → code_context_control-2.39.1}/README.md +0 -0
  10. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/__init__.py +0 -0
  11. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/_hook_utils.py +0 -0
  12. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/c3.py +0 -0
  13. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/commands/__init__.py +0 -0
  14. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/commands/common.py +0 -0
  15. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/commands/parser.py +0 -0
  16. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/docs.html +0 -0
  17. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/edits.html +0 -0
  18. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/guide/bitbucket.html +0 -0
  19. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/guide/getting-started.html +0 -0
  20. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/guide/index.html +0 -0
  21. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/guide/oracle.html +0 -0
  22. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/guide/shared.css +0 -0
  23. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/guide/tools.html +0 -0
  24. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/guide/workflow.html +0 -0
  25. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hook_auto_snapshot.py +0 -0
  26. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hook_c3_signal.py +0 -0
  27. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hook_c3read.py +0 -0
  28. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hook_edit_ledger.py +0 -0
  29. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hook_edit_unlock.py +0 -0
  30. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hook_filter.py +0 -0
  31. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hook_ghost_files.py +0 -0
  32. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hook_pretool_enforce.py +0 -0
  33. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hook_read.py +0 -0
  34. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hook_session_stats.py +0 -0
  35. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hook_terse_advisor.py +0 -0
  36. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hub.html +0 -0
  37. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/hub_server.py +0 -0
  38. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/mcp_proxy.py +0 -0
  39. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/mcp_server.py +0 -0
  40. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/server.py +0 -0
  41. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/__init__.py +0 -0
  42. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/_helpers.py +0 -0
  43. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/agent.py +0 -0
  44. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/bitbucket.py +0 -0
  45. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/compress.py +0 -0
  46. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/edit.py +0 -0
  47. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/edits.py +0 -0
  48. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/filter.py +0 -0
  49. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/impact.py +0 -0
  50. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/memory.py +0 -0
  51. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/project.py +0 -0
  52. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/read.py +0 -0
  53. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/search.py +0 -0
  54. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/session.py +0 -0
  55. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/shell.py +0 -0
  56. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/status.py +0 -0
  57. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/tools/validate.py +0 -0
  58. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/api.js +0 -0
  59. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/app.js +0 -0
  60. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/components/bitbucket.js +0 -0
  61. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/components/chat.js +0 -0
  62. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/components/dashboard.js +0 -0
  63. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/components/edits.js +0 -0
  64. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/components/instructions.js +0 -0
  65. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/components/memory.js +0 -0
  66. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/components/sessions.js +0 -0
  67. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/components/settings.js +0 -0
  68. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/components/sidebar.js +0 -0
  69. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/icons.js +0 -0
  70. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/shared.js +0 -0
  71. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui/theme.js +0 -0
  72. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui.html +0 -0
  73. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui_legacy.html +0 -0
  74. {code_context_control-2.39.0 → code_context_control-2.39.1}/cli/ui_nano.html +0 -0
  75. {code_context_control-2.39.0 → code_context_control-2.39.1}/code_context_control.egg-info/dependency_links.txt +0 -0
  76. {code_context_control-2.39.0 → code_context_control-2.39.1}/code_context_control.egg-info/entry_points.txt +0 -0
  77. {code_context_control-2.39.0 → code_context_control-2.39.1}/code_context_control.egg-info/requires.txt +0 -0
  78. {code_context_control-2.39.0 → code_context_control-2.39.1}/code_context_control.egg-info/top_level.txt +0 -0
  79. {code_context_control-2.39.0 → code_context_control-2.39.1}/core/__init__.py +0 -0
  80. {code_context_control-2.39.0 → code_context_control-2.39.1}/core/config.py +0 -0
  81. {code_context_control-2.39.0 → code_context_control-2.39.1}/core/ide.py +0 -0
  82. {code_context_control-2.39.0 → code_context_control-2.39.1}/core/mcp_toml.py +0 -0
  83. {code_context_control-2.39.0 → code_context_control-2.39.1}/core/web_security.py +0 -0
  84. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/__init__.py +0 -0
  85. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/config.py +0 -0
  86. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/mcp_oracle.py +0 -0
  87. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/oracle.html +0 -0
  88. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/oracle_server.py +0 -0
  89. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/__init__.py +0 -0
  90. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/activity_reporter.py +0 -0
  91. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/api_auth.py +0 -0
  92. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/c3_bridge.py +0 -0
  93. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/chat_engine.py +0 -0
  94. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/chat_store.py +0 -0
  95. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/cross_memory.py +0 -0
  96. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/federated_graph.py +0 -0
  97. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/health_checker.py +0 -0
  98. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/insight_engine.py +0 -0
  99. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/memory_reader.py +0 -0
  100. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/memory_writer.py +0 -0
  101. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/ollama_bridge.py +0 -0
  102. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/project_scanner.py +0 -0
  103. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/review_agent.py +0 -0
  104. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/tool_executor.py +0 -0
  105. {code_context_control-2.39.0 → code_context_control-2.39.1}/oracle/services/tool_registry.py +0 -0
  106. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/__init__.py +0 -0
  107. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/activity_log.py +0 -0
  108. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/agent_base.py +0 -0
  109. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/agents.py +0 -0
  110. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/auto_memory.py +0 -0
  111. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/bench/__init__.py +0 -0
  112. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/bench/external/__init__.py +0 -0
  113. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/bench/external/aider_polyglot.py +0 -0
  114. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/bench/external/swe_bench.py +0 -0
  115. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/benchmark_dashboard.py +0 -0
  116. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/bitbucket_client.py +0 -0
  117. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/bitbucket_credentials.py +0 -0
  118. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/claude_md.py +0 -0
  119. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/compressor.py +0 -0
  120. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/context_snapshot.py +0 -0
  121. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/conversation_store.py +0 -0
  122. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/doc_index.py +0 -0
  123. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/e2e_benchmark.py +0 -0
  124. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/e2e_evaluator.py +0 -0
  125. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/e2e_tasks.py +0 -0
  126. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/edit_ledger.py +0 -0
  127. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/embedding_index.py +0 -0
  128. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/error_reporting.py +0 -0
  129. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/file_memory.py +0 -0
  130. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/git_context.py +0 -0
  131. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/hub_service.py +0 -0
  132. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/indexer.py +0 -0
  133. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/memory.py +0 -0
  134. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/memory_consolidator.py +0 -0
  135. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/memory_graph.py +0 -0
  136. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/memory_grounder.py +0 -0
  137. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/memory_scorer.py +0 -0
  138. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/metrics.py +0 -0
  139. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/notifications.py +0 -0
  140. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/ollama_client.py +0 -0
  141. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/output_filter.py +0 -0
  142. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/parser.py +0 -0
  143. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/project_manager.py +0 -0
  144. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/project_runtime.py +0 -0
  145. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/protocol.py +0 -0
  146. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/proxy_state.py +0 -0
  147. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/retrieval_broker.py +0 -0
  148. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/router.py +0 -0
  149. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/runtime.py +0 -0
  150. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/session_benchmark.py +0 -0
  151. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/session_manager.py +0 -0
  152. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/session_preloader.py +0 -0
  153. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/text_index.py +0 -0
  154. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/tool_classifier.py +0 -0
  155. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/transcript_index.py +0 -0
  156. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/validation_cache.py +0 -0
  157. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/vector_store.py +0 -0
  158. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/version_tracker.py +0 -0
  159. {code_context_control-2.39.0 → code_context_control-2.39.1}/services/watcher.py +0 -0
  160. {code_context_control-2.39.0 → code_context_control-2.39.1}/setup.cfg +0 -0
  161. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_activity_reporter.py +0 -0
  162. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_aider_polyglot.py +0 -0
  163. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_bitbucket_cli_smoke.py +0 -0
  164. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_bitbucket_client.py +0 -0
  165. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_bitbucket_credentials.py +0 -0
  166. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_bitbucket_tool.py +0 -0
  167. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_c3_shell.py +0 -0
  168. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_claude_md_merge.py +0 -0
  169. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_cli_smoke.py +0 -0
  170. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_e2e_benchmark.py +0 -0
  171. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_edit_ledger_hook.py +0 -0
  172. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_edit_normalization.py +0 -0
  173. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_enforcement_flip.py +0 -0
  174. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_federated_graph.py +0 -0
  175. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_ghost_files.py +0 -0
  176. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_git_branch_awareness.py +0 -0
  177. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_hub_server_smoke.py +0 -0
  178. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_install_mcp_entrypoint.py +0 -0
  179. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_lazy_store_init.py +0 -0
  180. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_mcp_host_guard.py +0 -0
  181. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_mcp_server_smoke.py +0 -0
  182. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_mcp_toml.py +0 -0
  183. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_memory_graph_api.py +0 -0
  184. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_memory_system.py +0 -0
  185. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_notification_discipline.py +0 -0
  186. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_oracle_api_auth.py +0 -0
  187. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_oracle_apikey_api.py +0 -0
  188. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_oracle_discovery_api.py +0 -0
  189. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_oracle_security_fixes.py +0 -0
  190. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_output_filter.py +0 -0
  191. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_permissions.py +0 -0
  192. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_project_manager.py +0 -0
  193. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_project_manager_merge.py +0 -0
  194. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_project_tool.py +0 -0
  195. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_read_coercion.py +0 -0
  196. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_service_durability.py +0 -0
  197. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_session_benchmark.py +0 -0
  198. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_session_budget.py +0 -0
  199. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_shell_robustness.py +0 -0
  200. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_swe_bench.py +0 -0
  201. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_tool_registry.py +0 -0
  202. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_upgrade_and_version.py +0 -0
  203. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_validate.py +0 -0
  204. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_web_security.py +0 -0
  205. {code_context_control-2.39.0 → code_context_control-2.39.1}/tests/test_windows_reliability.py +0 -0
  206. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/__init__.py +0 -0
  207. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/backend.py +0 -0
  208. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/main.py +0 -0
  209. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/__init__.py +0 -0
  210. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/benchmark_view.py +0 -0
  211. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/claudemd_view.py +0 -0
  212. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/compress_view.py +0 -0
  213. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/index_view.py +0 -0
  214. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/init_view.py +0 -0
  215. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/mcp_view.py +0 -0
  216. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/optimize_view.py +0 -0
  217. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/pipe_view.py +0 -0
  218. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/projects_view.py +0 -0
  219. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/search_view.py +0 -0
  220. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/session_view.py +0 -0
  221. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/stats.py +0 -0
  222. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/screens/ui_view.py +0 -0
  223. {code_context_control-2.39.0 → code_context_control-2.39.1}/tui/theme.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.39.0
3
+ Version: 2.39.1
4
4
  Summary: Local code-intelligence layer for AI coding tools (Claude Code, Codex, Gemini, Copilot). Retrieve less, read less, edit safer.
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -11,10 +11,12 @@ import os
11
11
  import shutil
12
12
  import subprocess
13
13
  import sys
14
+ import threading
14
15
  import time
15
16
  from pathlib import Path
16
17
 
17
18
  from core import count_tokens
19
+ from services.circuit_breaker import CircuitBreaker
18
20
 
19
21
  log = logging.getLogger(__name__)
20
22
 
@@ -252,11 +254,20 @@ def _handle_claude_delegate(task: str, task_type: str, context: str,
252
254
  file_path: str, svc, dcfg: dict, finalize) -> str:
253
255
  """Handle delegation via Claude Code CLI."""
254
256
  timeout = int(dcfg.get("claude_timeout", 90))
257
+ breaker = _backend_breaker("claude", dcfg)
258
+ if not breaker.allow():
259
+ return finalize("c3_delegate", {"task_type": task_type, "backend": "claude"},
260
+ "[delegate:degraded] Claude skipped after repeated failures; retrying in "
261
+ f"~{breaker.cooldown_remaining()}s. Run 'claude --version' to diagnose.",
262
+ "degraded")
255
263
  _log_progress(svc, f"[delegate] Routing {task_type} → Claude CLI...")
256
264
  output, ok = _run_claude(task, context, cwd=str(svc.project_path), timeout=timeout)
257
265
  if not ok:
266
+ if breaker.record_failure():
267
+ _notify_backend_degraded(svc, "claude", breaker)
258
268
  return finalize("c3_delegate", {"task_type": task_type, "backend": "claude"},
259
269
  output, "error")
270
+ breaker.record_success()
260
271
  return finalize("c3_delegate", {"task_type": task_type, "backend": "claude"},
261
272
  output, "ok")
262
273
 
@@ -681,6 +692,51 @@ DELEGATE_TASKS = {
681
692
  _delegate_cache: dict[str, tuple[str, int]] = {}
682
693
  _delegate_metrics = {"total_calls": 0, "tokens_saved": 0}
683
694
 
695
+ # Per-backend runtime circuit breakers. Distinct from the install-status flags
696
+ # (_gemini_available etc., which only answer "is the CLI on PATH"): these track
697
+ # *runtime* health so a broken-but-installed backend (expired auth, repeated
698
+ # timeouts) stops re-spawning a 90-120s subprocess on every call. Keyed by
699
+ # backend name and intentionally process-global — backend health (auth, CLI
700
+ # version) is a property of the host, not of any single project.
701
+ _backend_breakers: dict[str, CircuitBreaker] = {}
702
+ _backend_breakers_lock = threading.Lock()
703
+
704
+
705
+ def _backend_breaker(name: str, dcfg: dict | None = None) -> CircuitBreaker:
706
+ """Return (creating on first use) the runtime circuit breaker for a backend."""
707
+ with _backend_breakers_lock:
708
+ breaker = _backend_breakers.get(name)
709
+ if breaker is None:
710
+ cfg = dcfg or {}
711
+ breaker = CircuitBreaker(
712
+ name,
713
+ failure_threshold=int(cfg.get("breaker_failure_threshold", 3) or 3),
714
+ cooldown_seconds=float(cfg.get("breaker_cooldown_seconds", 60) or 60),
715
+ )
716
+ _backend_breakers[name] = breaker
717
+ return breaker
718
+
719
+
720
+ def _notify_backend_degraded(svc, name: str, breaker: CircuitBreaker) -> None:
721
+ """Surface a backend trip via the NotificationStore (best-effort, never raises)."""
722
+ notifications = getattr(svc, "notifications", None)
723
+ if notifications is None:
724
+ return
725
+ try:
726
+ notifications.add(
727
+ agent="c3",
728
+ severity="warning",
729
+ title=f"Delegate backend degraded: {name}",
730
+ message=(
731
+ f"{name} failed {breaker.failure_threshold}x consecutively; c3_delegate "
732
+ f"will skip it for ~{int(breaker.cooldown_seconds)}s instead of re-spawning "
733
+ f"the CLI. Run '{name} --version' to diagnose."
734
+ ),
735
+ replace_if_unacked=True,
736
+ )
737
+ except Exception:
738
+ pass
739
+
684
740
 
685
741
  def get_delegate_metrics() -> dict:
686
742
  return dict(_delegate_metrics)
@@ -765,6 +821,13 @@ def _handle_codex_delegate(task: str, task_type: str, context: str,
765
821
  "[delegate:error] Codex CLI not available. Run 'codex --version' to diagnose.",
766
822
  "unavailable")
767
823
 
824
+ breaker = _backend_breaker("codex", dcfg)
825
+ if not breaker.allow():
826
+ return finalize("c3_delegate", {"task_type": task_type, "backend": "codex"},
827
+ "[delegate:degraded] Codex skipped after repeated failures; retrying in "
828
+ f"~{breaker.cooldown_remaining()}s. Run 'codex --version' to diagnose.",
829
+ "degraded")
830
+
768
831
  # Resolve model/sandbox/reasoning from config or defaults
769
832
  cdef = CODEX_MODELS.get(task_type, CODEX_MODELS.get("ask", {}))
770
833
  model = dcfg.get("codex_default_model") or cdef.get("model", "gpt-5.3-codex-spark")
@@ -807,10 +870,13 @@ def _handle_codex_delegate(task: str, task_type: str, context: str,
807
870
  elapsed = round(time.monotonic() - t0, 1)
808
871
 
809
872
  if not ok:
873
+ if breaker.record_failure():
874
+ _notify_backend_degraded(svc, "codex", breaker)
810
875
  return finalize("c3_delegate",
811
876
  {"task_type": task_type, "backend": "codex", "model": model, "elapsed": f"{elapsed}s"},
812
877
  output, "error")
813
878
 
879
+ breaker.record_success()
814
880
  _delegate_metrics["total_calls"] += 1
815
881
  _delegate_cache[ckey] = (output, count_tokens(output))
816
882
 
@@ -880,6 +946,13 @@ def _handle_gemini_delegate(task: str, task_type: str, context: str,
880
946
  "[delegate:error] Gemini CLI not available. Run 'gemini --version' to diagnose.",
881
947
  "unavailable")
882
948
 
949
+ breaker = _backend_breaker("gemini", dcfg)
950
+ if not breaker.allow():
951
+ return finalize("c3_delegate", {"task_type": task_type, "backend": "gemini"},
952
+ "[delegate:degraded] Gemini skipped after repeated failures; retrying in "
953
+ f"~{breaker.cooldown_remaining()}s. Run 'gemini --version' to diagnose.",
954
+ "degraded")
955
+
883
956
  # Resolve model from config or defaults
884
957
  gdef = GEMINI_MODELS.get(task_type, GEMINI_MODELS.get("ask", {}))
885
958
  model = dcfg.get("gemini_default_model") or gdef.get("model", "gemini-2.5-flash")
@@ -919,10 +992,13 @@ def _handle_gemini_delegate(task: str, task_type: str, context: str,
919
992
  elapsed = round(time.monotonic() - t0, 1)
920
993
 
921
994
  if not ok:
995
+ if breaker.record_failure():
996
+ _notify_backend_degraded(svc, "gemini", breaker)
922
997
  return finalize("c3_delegate",
923
998
  {"task_type": task_type, "backend": "gemini", "model": model, "elapsed": f"{elapsed}s"},
924
999
  output, "error")
925
1000
 
1001
+ breaker.record_success()
926
1002
  _delegate_metrics["total_calls"] += 1
927
1003
  _delegate_cache[ckey] = (output, count_tokens(output))
928
1004
 
@@ -1068,9 +1144,11 @@ def handle_delegate(task: str, task_type: str, context: str, file_path: str,
1068
1144
  _gemini_avail = (_gemini_available is True) or (
1069
1145
  _gemini_available is None and task_type not in _light_tasks and _is_gemini_on_path()
1070
1146
  )
1071
- if task_type in heavy_codex and _codex_avail and _codex_available is not False:
1147
+ if (task_type in heavy_codex and _codex_avail and _codex_available is not False
1148
+ and _backend_breaker("codex", dcfg).allow()):
1072
1149
  backend = "codex"
1073
- elif task_type in heavy_gemini and _gemini_avail and _gemini_available is not False:
1150
+ elif (task_type in heavy_gemini and _gemini_avail and _gemini_available is not False
1151
+ and _backend_breaker("gemini", dcfg).allow()):
1074
1152
  backend = "gemini"
1075
1153
  else:
1076
1154
  backend = "ollama"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.39.0
3
+ Version: 2.39.1
4
4
  Summary: Local code-intelligence layer for AI coding tools (Claude Code, Codex, Gemini, Copilot). Retrieve less, read less, edit safer.
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -108,6 +108,7 @@ services/auto_memory.py
108
108
  services/benchmark_dashboard.py
109
109
  services/bitbucket_client.py
110
110
  services/bitbucket_credentials.py
111
+ services/circuit_breaker.py
111
112
  services/claude_md.py
112
113
  services/compressor.py
113
114
  services/context_snapshot.py
@@ -161,6 +162,7 @@ tests/test_bitbucket_client.py
161
162
  tests/test_bitbucket_credentials.py
162
163
  tests/test_bitbucket_tool.py
163
164
  tests/test_c3_shell.py
165
+ tests/test_circuit_breaker.py
164
166
  tests/test_claude_md_merge.py
165
167
  tests/test_cli_smoke.py
166
168
  tests/test_e2e_benchmark.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-context-control"
7
- version = "2.39.0"
7
+ version = "2.39.1"
8
8
  description = "Local code-intelligence layer for AI coding tools (Claude Code, Codex, Gemini, Copilot). Retrieve less, read less, edit safer."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,86 @@
1
+ """Lightweight thread-safe circuit breaker for flapping subsystems.
2
+
3
+ Borrowed in spirit from Headroom's TransformPipeline breaker: after N
4
+ consecutive failures a subsystem is treated as unhealthy and calls are
5
+ short-circuited for a cooldown window instead of re-running (and re-failing)
6
+ the expensive operation every time. A single success closes the breaker.
7
+
8
+ First consumer: c3_delegate, to stop re-spawning a broken-but-installed CLI
9
+ backend (a 90-120s subprocess timeout) on every call. Deliberately
10
+ dependency-free so any call-time subsystem (e.g. the Ollama embed/generate
11
+ path) can reuse it later.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import threading
16
+ import time
17
+
18
+
19
+ class CircuitBreaker:
20
+ """Consecutive-failure breaker: closed, opens after N fails, half-opens to probe after a cooldown."""
21
+
22
+ def __init__(
23
+ self,
24
+ name: str = "",
25
+ *,
26
+ failure_threshold: int = 3,
27
+ cooldown_seconds: float = 60.0,
28
+ ) -> None:
29
+ self.name = name
30
+ self.failure_threshold = max(1, int(failure_threshold))
31
+ self.cooldown_seconds = max(0.0, float(cooldown_seconds))
32
+ self._lock = threading.Lock()
33
+ self._failures = 0
34
+ self._open = False
35
+ self._opened_at = 0.0
36
+
37
+ def allow(self) -> bool:
38
+ """Return True if a call may proceed.
39
+
40
+ An open breaker permits a single probe once the cooldown has elapsed
41
+ (half-open); the next ``record_success``/``record_failure`` resolves it.
42
+ """
43
+ with self._lock:
44
+ if not self._open:
45
+ return True
46
+ return (time.monotonic() - self._opened_at) >= self.cooldown_seconds
47
+
48
+ def record_success(self) -> None:
49
+ """Reset the breaker after a healthy call."""
50
+ with self._lock:
51
+ self._failures = 0
52
+ self._open = False
53
+ self._opened_at = 0.0
54
+
55
+ def record_failure(self) -> bool:
56
+ """Count a failed call.
57
+
58
+ Returns True iff this failure *just* tripped the breaker into the open
59
+ state, so callers can emit a one-shot notification on that edge. A failed
60
+ half-open probe restarts the cooldown but does not re-trip.
61
+ """
62
+ with self._lock:
63
+ self._failures += 1
64
+ if not self._open and self._failures >= self.failure_threshold:
65
+ self._open = True
66
+ self._opened_at = time.monotonic()
67
+ return True
68
+ if self._open:
69
+ self._opened_at = time.monotonic()
70
+ return False
71
+
72
+ def cooldown_remaining(self) -> int:
73
+ """Whole seconds left before the next probe is allowed (0 if closed/elapsed)."""
74
+ with self._lock:
75
+ if not self._open:
76
+ return 0
77
+ remaining = self.cooldown_seconds - (time.monotonic() - self._opened_at)
78
+ return max(0, int(round(remaining)))
79
+
80
+ @property
81
+ def is_open(self) -> bool:
82
+ """True while calls are actively being short-circuited (open and within cooldown)."""
83
+ with self._lock:
84
+ if not self._open:
85
+ return False
86
+ return (time.monotonic() - self._opened_at) < self.cooldown_seconds
@@ -0,0 +1,103 @@
1
+ """Tests for the CircuitBreaker primitive and the c3_delegate demote-on-failure wiring."""
2
+ import time
3
+
4
+ from services.circuit_breaker import CircuitBreaker
5
+
6
+
7
+ def test_closed_until_threshold_then_opens():
8
+ br = CircuitBreaker("x", failure_threshold=3, cooldown_seconds=60)
9
+ assert br.allow()
10
+ assert br.record_failure() is False # 1
11
+ assert br.record_failure() is False # 2
12
+ assert br.allow() # still closed below threshold
13
+ assert br.record_failure() is True # 3 -> trips it open at threshold
14
+ assert not br.allow() # open, within cooldown
15
+ assert br.is_open
16
+
17
+
18
+ def test_success_closes_breaker():
19
+ br = CircuitBreaker("x", failure_threshold=2, cooldown_seconds=60)
20
+ br.record_failure()
21
+ assert br.record_failure() is True
22
+ assert not br.allow()
23
+ br.record_success()
24
+ assert br.allow()
25
+ assert br.cooldown_remaining() == 0
26
+ assert not br.is_open
27
+
28
+
29
+ def test_half_open_probe_after_cooldown():
30
+ br = CircuitBreaker("x", failure_threshold=1, cooldown_seconds=0.05)
31
+ assert br.record_failure() is True
32
+ assert not br.allow() # blocked within cooldown
33
+ time.sleep(0.08)
34
+ assert br.allow() # half-open: one probe allowed
35
+ assert br.record_failure() is False # failed probe re-arms, not a new trip
36
+ assert not br.allow()
37
+
38
+
39
+ def test_cooldown_remaining_within_bounds():
40
+ br = CircuitBreaker("x", failure_threshold=1, cooldown_seconds=30)
41
+ br.record_failure()
42
+ assert 0 < br.cooldown_remaining() <= 30
43
+
44
+
45
+ class _FakeNotifications:
46
+ def __init__(self):
47
+ self.entries = []
48
+
49
+ def add(self, **kwargs):
50
+ self.entries.append(kwargs)
51
+ return kwargs
52
+
53
+
54
+ class _FakeSvc:
55
+ def __init__(self):
56
+ self.project_path = "."
57
+ self.delegate_config = {
58
+ "gemini_enabled": True,
59
+ "auto_compress": False,
60
+ "breaker_failure_threshold": 3,
61
+ "breaker_cooldown_seconds": 60,
62
+ }
63
+ self.notifications = _FakeNotifications()
64
+ self.compressor = None
65
+ self._agent_progress_cb = None
66
+
67
+
68
+ def _finalize(_tool, _meta, _output, status):
69
+ return status
70
+
71
+
72
+ def test_gemini_demotes_after_repeated_failures(monkeypatch):
73
+ """The core bug fix: a broken backend must stop re-spawning the CLI every call."""
74
+ from cli.tools import delegate
75
+
76
+ # Isolate module-global breaker + cache + availability state.
77
+ delegate._backend_breakers.clear()
78
+ delegate._delegate_cache.clear()
79
+ monkeypatch.setattr(delegate, "_gemini_available", True, raising=False)
80
+
81
+ calls = {"run": 0}
82
+
83
+ def fake_run_gemini(*_a, **_k):
84
+ calls["run"] += 1
85
+ return ("boom", False, {})
86
+
87
+ monkeypatch.setattr(delegate, "_run_gemini", fake_run_gemini)
88
+
89
+ svc = _FakeSvc()
90
+ dcfg = svc.delegate_config
91
+
92
+ # Three real failures trip the breaker (threshold=3).
93
+ for _ in range(3):
94
+ assert delegate._handle_gemini_delegate("t", "ask", "", "", svc, dcfg, _finalize) == "error"
95
+ assert calls["run"] == 3
96
+
97
+ # Fourth call: breaker open -> short-circuit, NO subprocess re-spawn.
98
+ assert delegate._handle_gemini_delegate("t", "ask", "", "", svc, dcfg, _finalize) == "degraded"
99
+ assert calls["run"] == 3
100
+
101
+ # The trip emitted exactly one degradation notification.
102
+ degraded = [e for e in svc.notifications.entries if "degraded" in e.get("title", "").lower()]
103
+ assert len(degraded) == 1