code-context-control 2.39.1__tar.gz → 2.40.0__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 (224) hide show
  1. {code_context_control-2.39.1/code_context_control.egg-info → code_context_control-2.40.0}/PKG-INFO +16 -3
  2. {code_context_control-2.39.1 → code_context_control-2.40.0}/README.md +15 -2
  3. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/c3.py +36 -11
  4. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/commands/parser.py +1 -0
  5. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/bitbucket.py +25 -20
  6. {code_context_control-2.39.1 → code_context_control-2.40.0/code_context_control.egg-info}/PKG-INFO +16 -3
  7. {code_context_control-2.39.1 → code_context_control-2.40.0}/code_context_control.egg-info/SOURCES.txt +1 -0
  8. {code_context_control-2.39.1 → code_context_control-2.40.0}/core/config.py +34 -8
  9. {code_context_control-2.39.1 → code_context_control-2.40.0}/pyproject.toml +1 -1
  10. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/bitbucket_client.py +46 -11
  11. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/claude_md.py +1 -1
  12. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_bitbucket_client.py +63 -4
  13. code_context_control-2.40.0/tests/test_bitbucket_config_fallback.py +70 -0
  14. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_bitbucket_tool.py +10 -0
  15. {code_context_control-2.39.1 → code_context_control-2.40.0}/LICENSE +0 -0
  16. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/__init__.py +0 -0
  17. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/_hook_utils.py +0 -0
  18. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/commands/__init__.py +0 -0
  19. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/commands/common.py +0 -0
  20. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/docs.html +0 -0
  21. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/edits.html +0 -0
  22. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/guide/bitbucket.html +0 -0
  23. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/guide/getting-started.html +0 -0
  24. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/guide/index.html +0 -0
  25. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/guide/oracle.html +0 -0
  26. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/guide/shared.css +0 -0
  27. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/guide/tools.html +0 -0
  28. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/guide/workflow.html +0 -0
  29. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hook_auto_snapshot.py +0 -0
  30. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hook_c3_signal.py +0 -0
  31. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hook_c3read.py +0 -0
  32. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hook_edit_ledger.py +0 -0
  33. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hook_edit_unlock.py +0 -0
  34. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hook_filter.py +0 -0
  35. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hook_ghost_files.py +0 -0
  36. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hook_pretool_enforce.py +0 -0
  37. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hook_read.py +0 -0
  38. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hook_session_stats.py +0 -0
  39. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hook_terse_advisor.py +0 -0
  40. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hub.html +0 -0
  41. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/hub_server.py +0 -0
  42. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/mcp_proxy.py +0 -0
  43. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/mcp_server.py +0 -0
  44. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/server.py +0 -0
  45. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/__init__.py +0 -0
  46. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/_helpers.py +0 -0
  47. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/agent.py +0 -0
  48. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/compress.py +0 -0
  49. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/delegate.py +0 -0
  50. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/edit.py +0 -0
  51. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/edits.py +0 -0
  52. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/filter.py +0 -0
  53. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/impact.py +0 -0
  54. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/memory.py +0 -0
  55. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/project.py +0 -0
  56. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/read.py +0 -0
  57. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/search.py +0 -0
  58. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/session.py +0 -0
  59. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/shell.py +0 -0
  60. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/status.py +0 -0
  61. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/tools/validate.py +0 -0
  62. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/api.js +0 -0
  63. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/app.js +0 -0
  64. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/components/bitbucket.js +0 -0
  65. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/components/chat.js +0 -0
  66. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/components/dashboard.js +0 -0
  67. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/components/edits.js +0 -0
  68. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/components/instructions.js +0 -0
  69. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/components/memory.js +0 -0
  70. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/components/sessions.js +0 -0
  71. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/components/settings.js +0 -0
  72. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/components/sidebar.js +0 -0
  73. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/icons.js +0 -0
  74. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/shared.js +0 -0
  75. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui/theme.js +0 -0
  76. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui.html +0 -0
  77. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui_legacy.html +0 -0
  78. {code_context_control-2.39.1 → code_context_control-2.40.0}/cli/ui_nano.html +0 -0
  79. {code_context_control-2.39.1 → code_context_control-2.40.0}/code_context_control.egg-info/dependency_links.txt +0 -0
  80. {code_context_control-2.39.1 → code_context_control-2.40.0}/code_context_control.egg-info/entry_points.txt +0 -0
  81. {code_context_control-2.39.1 → code_context_control-2.40.0}/code_context_control.egg-info/requires.txt +0 -0
  82. {code_context_control-2.39.1 → code_context_control-2.40.0}/code_context_control.egg-info/top_level.txt +0 -0
  83. {code_context_control-2.39.1 → code_context_control-2.40.0}/core/__init__.py +0 -0
  84. {code_context_control-2.39.1 → code_context_control-2.40.0}/core/ide.py +0 -0
  85. {code_context_control-2.39.1 → code_context_control-2.40.0}/core/mcp_toml.py +0 -0
  86. {code_context_control-2.39.1 → code_context_control-2.40.0}/core/web_security.py +0 -0
  87. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/__init__.py +0 -0
  88. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/config.py +0 -0
  89. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/mcp_oracle.py +0 -0
  90. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/oracle.html +0 -0
  91. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/oracle_server.py +0 -0
  92. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/__init__.py +0 -0
  93. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/activity_reporter.py +0 -0
  94. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/api_auth.py +0 -0
  95. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/c3_bridge.py +0 -0
  96. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/chat_engine.py +0 -0
  97. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/chat_store.py +0 -0
  98. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/cross_memory.py +0 -0
  99. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/federated_graph.py +0 -0
  100. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/health_checker.py +0 -0
  101. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/insight_engine.py +0 -0
  102. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/memory_reader.py +0 -0
  103. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/memory_writer.py +0 -0
  104. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/ollama_bridge.py +0 -0
  105. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/project_scanner.py +0 -0
  106. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/review_agent.py +0 -0
  107. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/tool_executor.py +0 -0
  108. {code_context_control-2.39.1 → code_context_control-2.40.0}/oracle/services/tool_registry.py +0 -0
  109. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/__init__.py +0 -0
  110. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/activity_log.py +0 -0
  111. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/agent_base.py +0 -0
  112. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/agents.py +0 -0
  113. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/auto_memory.py +0 -0
  114. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/bench/__init__.py +0 -0
  115. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/bench/external/__init__.py +0 -0
  116. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/bench/external/aider_polyglot.py +0 -0
  117. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/bench/external/swe_bench.py +0 -0
  118. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/benchmark_dashboard.py +0 -0
  119. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/bitbucket_credentials.py +0 -0
  120. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/circuit_breaker.py +0 -0
  121. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/compressor.py +0 -0
  122. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/context_snapshot.py +0 -0
  123. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/conversation_store.py +0 -0
  124. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/doc_index.py +0 -0
  125. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/e2e_benchmark.py +0 -0
  126. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/e2e_evaluator.py +0 -0
  127. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/e2e_tasks.py +0 -0
  128. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/edit_ledger.py +0 -0
  129. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/embedding_index.py +0 -0
  130. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/error_reporting.py +0 -0
  131. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/file_memory.py +0 -0
  132. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/git_context.py +0 -0
  133. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/hub_service.py +0 -0
  134. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/indexer.py +0 -0
  135. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/memory.py +0 -0
  136. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/memory_consolidator.py +0 -0
  137. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/memory_graph.py +0 -0
  138. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/memory_grounder.py +0 -0
  139. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/memory_scorer.py +0 -0
  140. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/metrics.py +0 -0
  141. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/notifications.py +0 -0
  142. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/ollama_client.py +0 -0
  143. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/output_filter.py +0 -0
  144. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/parser.py +0 -0
  145. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/project_manager.py +0 -0
  146. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/project_runtime.py +0 -0
  147. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/protocol.py +0 -0
  148. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/proxy_state.py +0 -0
  149. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/retrieval_broker.py +0 -0
  150. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/router.py +0 -0
  151. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/runtime.py +0 -0
  152. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/session_benchmark.py +0 -0
  153. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/session_manager.py +0 -0
  154. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/session_preloader.py +0 -0
  155. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/text_index.py +0 -0
  156. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/tool_classifier.py +0 -0
  157. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/transcript_index.py +0 -0
  158. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/validation_cache.py +0 -0
  159. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/vector_store.py +0 -0
  160. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/version_tracker.py +0 -0
  161. {code_context_control-2.39.1 → code_context_control-2.40.0}/services/watcher.py +0 -0
  162. {code_context_control-2.39.1 → code_context_control-2.40.0}/setup.cfg +0 -0
  163. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_activity_reporter.py +0 -0
  164. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_aider_polyglot.py +0 -0
  165. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_bitbucket_cli_smoke.py +0 -0
  166. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_bitbucket_credentials.py +0 -0
  167. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_c3_shell.py +0 -0
  168. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_circuit_breaker.py +0 -0
  169. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_claude_md_merge.py +0 -0
  170. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_cli_smoke.py +0 -0
  171. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_e2e_benchmark.py +0 -0
  172. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_edit_ledger_hook.py +0 -0
  173. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_edit_normalization.py +0 -0
  174. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_enforcement_flip.py +0 -0
  175. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_federated_graph.py +0 -0
  176. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_ghost_files.py +0 -0
  177. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_git_branch_awareness.py +0 -0
  178. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_hub_server_smoke.py +0 -0
  179. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_install_mcp_entrypoint.py +0 -0
  180. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_lazy_store_init.py +0 -0
  181. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_mcp_host_guard.py +0 -0
  182. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_mcp_server_smoke.py +0 -0
  183. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_mcp_toml.py +0 -0
  184. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_memory_graph_api.py +0 -0
  185. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_memory_system.py +0 -0
  186. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_notification_discipline.py +0 -0
  187. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_oracle_api_auth.py +0 -0
  188. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_oracle_apikey_api.py +0 -0
  189. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_oracle_discovery_api.py +0 -0
  190. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_oracle_security_fixes.py +0 -0
  191. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_output_filter.py +0 -0
  192. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_permissions.py +0 -0
  193. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_project_manager.py +0 -0
  194. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_project_manager_merge.py +0 -0
  195. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_project_tool.py +0 -0
  196. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_read_coercion.py +0 -0
  197. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_service_durability.py +0 -0
  198. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_session_benchmark.py +0 -0
  199. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_session_budget.py +0 -0
  200. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_shell_robustness.py +0 -0
  201. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_swe_bench.py +0 -0
  202. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_tool_registry.py +0 -0
  203. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_upgrade_and_version.py +0 -0
  204. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_validate.py +0 -0
  205. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_web_security.py +0 -0
  206. {code_context_control-2.39.1 → code_context_control-2.40.0}/tests/test_windows_reliability.py +0 -0
  207. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/__init__.py +0 -0
  208. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/backend.py +0 -0
  209. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/main.py +0 -0
  210. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/__init__.py +0 -0
  211. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/benchmark_view.py +0 -0
  212. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/claudemd_view.py +0 -0
  213. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/compress_view.py +0 -0
  214. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/index_view.py +0 -0
  215. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/init_view.py +0 -0
  216. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/mcp_view.py +0 -0
  217. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/optimize_view.py +0 -0
  218. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/pipe_view.py +0 -0
  219. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/projects_view.py +0 -0
  220. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/search_view.py +0 -0
  221. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/session_view.py +0 -0
  222. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/stats.py +0 -0
  223. {code_context_control-2.39.1 → code_context_control-2.40.0}/tui/screens/ui_view.py +0 -0
  224. {code_context_control-2.39.1 → code_context_control-2.40.0}/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.1
3
+ Version: 2.40.0
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
@@ -283,9 +283,12 @@ Access Token. Tokens live in the **OS keyring** (Windows Credential Manager,
283
283
  macOS Keychain, Linux Secret Service) — never in `.c3/config.json`.
284
284
 
285
285
  ```bash
286
- # One-time login per server
286
+ # One-time login per server (stored under this project's .c3/config.json)
287
287
  c3 bitbucket login --url https://bitbucket.example.com
288
- # prompts for username + PAT (masked)
288
+ # -> prompts for username + PAT (masked)
289
+
290
+ # ...or store it globally so every C3 project can use it
291
+ c3 bitbucket login --global --url https://bitbucket.example.com
289
292
 
290
293
  # Pin defaults so subsequent calls don't need project/repo
291
294
  c3 bitbucket set-default --project PROJ --repo my-service
@@ -294,6 +297,16 @@ c3 bitbucket set-default --project PROJ --repo my-service
294
297
  c3 bitbucket status
295
298
  ```
296
299
 
300
+ **Account resolution precedence:** the project's `.c3/config.json` wins, but when
301
+ it has no active account C3 falls back to the global `~/.c3/config.json`. So a
302
+ single `login --global` (or any login done from your home directory) is reusable
303
+ across every C3 project — the PAT always lives in the OS keyring, never on disk.
304
+
305
+ > **Upgrading:** stop the running `c3-mcp` server / CLI before `c3 upgrade`. A live
306
+ > process can hold package files open, leaving pip's `~`-prefixed backup dirs
307
+ > (`~ervices`, `~ools`, …) in `site-packages`; those are inert and safe to delete
308
+ > after the upgrade completes.
309
+
297
310
  The MCP tool dispatches by `action`. Read-only actions: `status`, `whoami`,
298
311
  `list_projects`, `list_repos`, `get_repo`, `list_prs`, `get_pr`, `get_pr_diff`,
299
312
  `get_pr_activities`, `list_branches`, `list_commits`, `list_activity`,
@@ -221,9 +221,12 @@ Access Token. Tokens live in the **OS keyring** (Windows Credential Manager,
221
221
  macOS Keychain, Linux Secret Service) — never in `.c3/config.json`.
222
222
 
223
223
  ```bash
224
- # One-time login per server
224
+ # One-time login per server (stored under this project's .c3/config.json)
225
225
  c3 bitbucket login --url https://bitbucket.example.com
226
- # prompts for username + PAT (masked)
226
+ # -> prompts for username + PAT (masked)
227
+
228
+ # ...or store it globally so every C3 project can use it
229
+ c3 bitbucket login --global --url https://bitbucket.example.com
227
230
 
228
231
  # Pin defaults so subsequent calls don't need project/repo
229
232
  c3 bitbucket set-default --project PROJ --repo my-service
@@ -232,6 +235,16 @@ c3 bitbucket set-default --project PROJ --repo my-service
232
235
  c3 bitbucket status
233
236
  ```
234
237
 
238
+ **Account resolution precedence:** the project's `.c3/config.json` wins, but when
239
+ it has no active account C3 falls back to the global `~/.c3/config.json`. So a
240
+ single `login --global` (or any login done from your home directory) is reusable
241
+ across every C3 project — the PAT always lives in the OS keyring, never on disk.
242
+
243
+ > **Upgrading:** stop the running `c3-mcp` server / CLI before `c3 upgrade`. A live
244
+ > process can hold package files open, leaving pip's `~`-prefixed backup dirs
245
+ > (`~ervices`, `~ools`, …) in `site-packages`; those are inert and safe to delete
246
+ > after the upgrade completes.
247
+
235
248
  The MCP tool dispatches by `action`. Read-only actions: `status`, `whoami`,
236
249
  `list_projects`, `list_repos`, `get_repo`, `list_prs`, `get_pr`, `get_pr_diff`,
237
250
  `get_pr_activities`, `list_branches`, `list_commits`, `list_activity`,
@@ -85,7 +85,7 @@ console = Console() if HAS_RICH else None
85
85
  # Config
86
86
  CONFIG_DIR = ".c3"
87
87
  CONFIG_FILE = ".c3/config.json"
88
- __version__ = "2.39.0"
88
+ __version__ = "2.40.0"
89
89
 
90
90
 
91
91
  def _command_deps() -> CommandDeps:
@@ -5577,14 +5577,19 @@ def _bb_cmd_login(args, project_path: str) -> None:
5577
5577
  from services import bitbucket_credentials as bb_creds
5578
5578
  from services.bitbucket_client import BitbucketDataCenterClient, BitbucketError
5579
5579
 
5580
+ # --global stores the account in ~/.c3/config.json so it is reusable in
5581
+ # every C3 project (load_bitbucket_config falls back to it automatically).
5582
+ if getattr(args, "use_global", False):
5583
+ project_path = str(Path.home())
5584
+
5580
5585
  base_url = (args.url or "").rstrip("/")
5581
5586
  username = args.username or input(f"Username for {base_url}: ").strip()
5582
5587
  if not username:
5583
- print("Login cancelled username required.")
5588
+ print("Login cancelled -- username required.")
5584
5589
  return
5585
5590
  token = args.token or getpass.getpass(f"Personal Access Token for {username}: ").strip()
5586
5591
  if not token:
5587
- print("Login cancelled token required.")
5592
+ print("Login cancelled -- token required.")
5588
5593
  return
5589
5594
 
5590
5595
  try:
@@ -5600,10 +5605,13 @@ def _bb_cmd_login(args, project_path: str) -> None:
5600
5605
  if getattr(args, "insecure", False):
5601
5606
  bb_creds.set_verify_tls(False, project_path=project_path)
5602
5607
 
5603
- print(f"[OK] Stored credentials for {username}@{base_url}")
5608
+ scope = "global (~/.c3)" if getattr(args, "use_global", False) else "project"
5609
+ print(f"[OK] Stored credentials for {username}@{base_url} [{scope}]")
5604
5610
 
5605
- # Connection probe non-fatal if it fails (token might be valid but
5606
- # network blocked at this moment).
5611
+ # Connection probe -- non-fatal if it fails (token might be valid but the
5612
+ # network is blocked right now). Gate success on application-properties
5613
+ # only; whoami enrichment is best-effort so a valid login never prints a
5614
+ # failure (Bitbucket DC has no /users/me).
5607
5615
  try:
5608
5616
  client = BitbucketDataCenterClient(
5609
5617
  base_url=base_url, token=token,
@@ -5611,12 +5619,20 @@ def _bb_cmd_login(args, project_path: str) -> None:
5611
5619
  )
5612
5620
  props = client.application_properties()
5613
5621
  version = props.get("version", "?")
5614
- user = client.whoami()
5615
5622
  print(f" Server: {version} ({base_url})")
5616
- print(f" Auth as: {user.get('displayName', username)} <{user.get('emailAddress', '?')}>")
5617
5623
  except BitbucketError as exc:
5618
5624
  print(f"[warn] Connection probe failed: {exc}")
5619
- print(" Token saved anyway re-test with `c3 bitbucket status`.")
5625
+ print(" Token saved anyway -- re-test with `c3 bitbucket status`.")
5626
+ return
5627
+ try:
5628
+ user = client.whoami()
5629
+ if user:
5630
+ print(
5631
+ f" Auth as: {user.get('displayName', username)} "
5632
+ f"<{user.get('emailAddress', '?')}>"
5633
+ )
5634
+ except BitbucketError:
5635
+ pass
5620
5636
 
5621
5637
 
5622
5638
  def _bb_cmd_logout(args, project_path: str) -> None:
@@ -5661,7 +5677,7 @@ def _bb_cmd_status(args, project_path: str) -> None:
5661
5677
  return
5662
5678
  token = bb_creds.load_token(active["base_url"], active["username"])
5663
5679
  if not token:
5664
- print(" Connection: FAIL no token in keyring")
5680
+ print(" Connection: FAIL -- no token in keyring")
5665
5681
  return
5666
5682
  try:
5667
5683
  client = BitbucketDataCenterClient(
@@ -5671,7 +5687,7 @@ def _bb_cmd_status(args, project_path: str) -> None:
5671
5687
  props = client.application_properties()
5672
5688
  print(f" Connection: OK (version {props.get('version','?')})")
5673
5689
  except BitbucketError as exc:
5674
- print(f" Connection: FAIL {exc}")
5690
+ print(f" Connection: FAIL -- {exc}")
5675
5691
 
5676
5692
 
5677
5693
  def _bb_cmd_use(args, project_path: str) -> None:
@@ -6571,6 +6587,15 @@ def _launch_tui() -> None:
6571
6587
 
6572
6588
 
6573
6589
  def main():
6590
+ # Force UTF-8 on the CLI streams so server-supplied text (PR titles, branch
6591
+ # names, diffs) and our own glyphs render cleanly on Windows cp1252 consoles
6592
+ # instead of raising UnicodeEncodeError or mojibaking.
6593
+ for _stream in (sys.stdout, sys.stderr):
6594
+ try:
6595
+ _stream.reconfigure(encoding="utf-8", errors="replace")
6596
+ except Exception:
6597
+ pass
6598
+
6574
6599
  try:
6575
6600
  from services import error_reporting
6576
6601
  error_reporting.init(component="c3-cli", version=__version__)
@@ -303,6 +303,7 @@ def build_parser(version: str, parse_cli_ide_arg):
303
303
  bb_login.add_argument("--token", help="Personal Access Token (prompted via getpass if omitted — preferred)")
304
304
  bb_login.add_argument("--no-set-active", action="store_true", help="Do not switch the active account to this one")
305
305
  bb_login.add_argument("--insecure", action="store_true", help="Disable TLS verification (self-signed certs)")
306
+ bb_login.add_argument("--global", dest="use_global", action="store_true", help="Store the account in the global ~/.c3/config.json so it is reusable in every C3 project")
306
307
  bb_login.add_argument("project_path", nargs="?", default=".")
307
308
 
308
309
  bb_logout = bb_subs.add_parser("logout", help="Remove a Bitbucket account from keyring + config")
@@ -50,7 +50,12 @@ def _cap(resp: str) -> str:
50
50
  candidate = "\n".join(lines) + "\n[truncated]"
51
51
  if count_tokens(candidate) <= _RESPONSE_TOKEN_CAP:
52
52
  return candidate
53
- return "\n".join(lines[:20]) + "\n[truncated]"
53
+ # A single over-long line never splits above; hard-clamp by characters as a
54
+ # final guard (~4 chars/token) so one huge line can't blow past the cap.
55
+ head = "\n".join(lines[:20])
56
+ if count_tokens(head) > _RESPONSE_TOKEN_CAP:
57
+ head = head[:_RESPONSE_TOKEN_CAP * 4]
58
+ return head + "\n[truncated]"
54
59
 
55
60
 
56
61
  def _build_client(svc) -> tuple[BitbucketDataCenterClient | None, str]:
@@ -111,20 +116,20 @@ def _format_pr(pr: dict) -> str:
111
116
  author = (pr.get("author") or {}).get("user", {}).get("name", "?")
112
117
  src = (pr.get("fromRef") or {}).get("displayId", "?")
113
118
  dst = (pr.get("toRef") or {}).get("displayId", "?")
114
- return f" #{pr_id} [{state:7}] {src} {dst} by {author}\n {title}"
119
+ return f" #{pr_id} [{state:7}] {src} -> {dst} by {author}\n {title}"
115
120
 
116
121
 
117
122
  def _format_pr_full(pr: dict) -> str:
118
123
  lines = [
119
- f"PR #{pr.get('id')} [{pr.get('state')}] {pr.get('title','')}",
120
- f" {(pr.get('fromRef') or {}).get('displayId')} {(pr.get('toRef') or {}).get('displayId')}",
124
+ f"PR #{pr.get('id')} [{pr.get('state')}] -- {pr.get('title','')}",
125
+ f" {(pr.get('fromRef') or {}).get('displayId')} -> {(pr.get('toRef') or {}).get('displayId')}",
121
126
  f" Author: {(pr.get('author') or {}).get('user',{}).get('displayName','?')}",
122
127
  f" Version: {pr.get('version')} | Open tasks: {pr.get('openTaskCount', 0)}",
123
128
  ]
124
129
  reviewers = pr.get("reviewers") or []
125
130
  if reviewers:
126
131
  rs = ", ".join(
127
- f"{r.get('user',{}).get('name','?')}({'' if r.get('approved') else '·'})"
132
+ f"{r.get('user',{}).get('name','?')}({'[x]' if r.get('approved') else '[ ]'})"
128
133
  for r in reviewers
129
134
  )
130
135
  lines.append(f" Reviewers: {rs}")
@@ -190,7 +195,7 @@ def _act_status(client: BitbucketDataCenterClient | None, err: str, svc) -> str:
190
195
  version = props.get("version", "?")
191
196
  out.append(f" Server : OK (version {version})")
192
197
  except BitbucketError as exc:
193
- out.append(f" Server : FAIL {exc}")
198
+ out.append(f" Server : FAIL -- {exc}")
194
199
  return "\n".join(out)
195
200
 
196
201
 
@@ -214,7 +219,7 @@ def _act_list_projects(client: BitbucketDataCenterClient, kwargs: dict) -> str:
214
219
  for p in projects[:50]:
215
220
  lines.append(f" {p.get('key','?'):16} {p.get('name','?')}")
216
221
  if len(projects) > 50:
217
- lines.append(f" +{len(projects) - 50} more")
222
+ lines.append(f" ... +{len(projects) - 50} more")
218
223
  return "\n".join(lines)
219
224
 
220
225
 
@@ -222,11 +227,11 @@ def _act_list_repos(client: BitbucketDataCenterClient, project: str) -> str:
222
227
  repos = client.list_repos(project)
223
228
  if not repos:
224
229
  return f"[bitbucket:repos] {project}: (none)"
225
- lines = [f"[bitbucket:repos] {project} {len(repos)} repo(s)"]
230
+ lines = [f"[bitbucket:repos] {project} - {len(repos)} repo(s)"]
226
231
  for r in repos[:80]:
227
232
  lines.append(f" {r.get('slug','?'):30} {r.get('name','?')}")
228
233
  if len(repos) > 80:
229
- lines.append(f" +{len(repos) - 80} more")
234
+ lines.append(f" ... +{len(repos) - 80} more")
230
235
  return "\n".join(lines)
231
236
 
232
237
 
@@ -252,7 +257,7 @@ def _act_list_prs(client, project: str, repo: str, kwargs: dict) -> str:
252
257
  )
253
258
  if not prs:
254
259
  return f"[bitbucket:prs] {project}/{repo} state={state}: (none)"
255
- out = [f"[bitbucket:prs] {project}/{repo} state={state} {len(prs)} PR(s)"]
260
+ out = [f"[bitbucket:prs] {project}/{repo} state={state} - {len(prs)} PR(s)"]
256
261
  for pr in prs:
257
262
  out.append(_format_pr(pr))
258
263
  return "\n".join(out)
@@ -266,7 +271,7 @@ def _act_get_pr(client, project: str, repo: str, pr_id: int) -> str:
266
271
  def _act_get_pr_diff(client, project: str, repo: str, pr_id: int, kwargs: dict) -> str:
267
272
  diff = client.get_pr_diff(project, repo, pr_id, context_lines=int(kwargs.get("context_lines", 3)))
268
273
  if len(diff) > _DIFF_PREVIEW_CHARS:
269
- diff = diff[:_DIFF_PREVIEW_CHARS] + "\n [diff truncated]"
274
+ diff = diff[:_DIFF_PREVIEW_CHARS] + "\n... [diff truncated]"
270
275
  return f"[bitbucket:diff] {project}/{repo}#{pr_id}\n{diff}"
271
276
 
272
277
 
@@ -274,7 +279,7 @@ def _act_get_pr_activities(client, project: str, repo: str, pr_id: int) -> str:
274
279
  acts = client.get_pr_activities(project, repo, pr_id)
275
280
  if not acts:
276
281
  return f"[bitbucket:pr-activity] {project}/{repo}#{pr_id}: (none)"
277
- out = [f"[bitbucket:pr-activity] {project}/{repo}#{pr_id} {len(acts)} event(s)"]
282
+ out = [f"[bitbucket:pr-activity] {project}/{repo}#{pr_id} - {len(acts)} event(s)"]
278
283
  for a in acts[:50]:
279
284
  out.append(_format_activity(a))
280
285
  return "\n".join(out)
@@ -341,11 +346,11 @@ def _act_list_branches(client, project: str, repo: str, kwargs: dict) -> str:
341
346
  branches = client.list_branches(project, repo, filter_text=kwargs.get("filter", ""))
342
347
  if not branches:
343
348
  return f"[bitbucket:branches] {project}/{repo}: (none)"
344
- out = [f"[bitbucket:branches] {project}/{repo} {len(branches)} branch(es)"]
349
+ out = [f"[bitbucket:branches] {project}/{repo} - {len(branches)} branch(es)"]
345
350
  for b in branches[:80]:
346
351
  out.append(_format_branch(b))
347
352
  if len(branches) > 80:
348
- out.append(f" +{len(branches) - 80} more")
353
+ out.append(f" ... +{len(branches) - 80} more")
349
354
  return "\n".join(out)
350
355
 
351
356
 
@@ -378,7 +383,7 @@ def _act_list_commits(client, project: str, repo: str, kwargs: dict) -> str:
378
383
  )
379
384
  if not commits:
380
385
  return f"[bitbucket:commits] {project}/{repo}: (none)"
381
- out = [f"[bitbucket:commits] {project}/{repo} {len(commits)} commit(s)"]
386
+ out = [f"[bitbucket:commits] {project}/{repo} - {len(commits)} commit(s)"]
382
387
  for c in commits:
383
388
  out.append(_format_commit(c))
384
389
  return "\n".join(out)
@@ -388,7 +393,7 @@ def _act_list_activity(client, project: str, repo: str, kwargs: dict) -> str:
388
393
  acts = client.list_repo_activities(project, repo, limit=int(kwargs.get("limit", 30)))
389
394
  if not acts:
390
395
  return f"[bitbucket:activity] {project}/{repo}: (none)"
391
- out = [f"[bitbucket:activity] {project}/{repo} {len(acts)} event(s)"]
396
+ out = [f"[bitbucket:activity] {project}/{repo} - {len(acts)} event(s)"]
392
397
  for a in acts:
393
398
  out.append(_format_commit(a))
394
399
  return "\n".join(out)
@@ -401,7 +406,7 @@ def _act_build_status(client, kwargs: dict) -> str:
401
406
  builds = client.get_build_status(commit)
402
407
  if not builds:
403
408
  return f"[bitbucket:build_status] {commit[:12]}: (none)"
404
- out = [f"[bitbucket:build_status] {commit[:12]} {len(builds)} build(s)"]
409
+ out = [f"[bitbucket:build_status] {commit[:12]} - {len(builds)} build(s)"]
405
410
  for b in builds:
406
411
  out.append(_format_build(b))
407
412
  return "\n".join(out)
@@ -429,10 +434,10 @@ def _act_list_webhooks(client, project: str, repo: str) -> str:
429
434
  hooks = client.list_webhooks(project, repo)
430
435
  if not hooks:
431
436
  return f"[bitbucket:webhooks] {project}/{repo}: (none)"
432
- out = [f"[bitbucket:webhooks] {project}/{repo} {len(hooks)} hook(s)"]
437
+ out = [f"[bitbucket:webhooks] {project}/{repo} - {len(hooks)} hook(s)"]
433
438
  for h in hooks:
434
439
  out.append(
435
- f" #{h.get('id')} [{'on' if h.get('active') else 'off'}] {h.get('name','?')} {h.get('url','?')}"
440
+ f" #{h.get('id')} [{'on' if h.get('active') else 'off'}] {h.get('name','?')} -> {h.get('url','?')}"
436
441
  )
437
442
  evs = h.get("events") or []
438
443
  if evs:
@@ -454,7 +459,7 @@ def _act_create_webhook(client, project: str, repo: str, kwargs: dict) -> str:
454
459
  active=bool(kwargs.get("active", True)),
455
460
  secret=kwargs.get("secret", ""),
456
461
  )
457
- return f"[bitbucket:webhook-created] {project}/{repo} #{res.get('id','?')} {name} {url}"
462
+ return f"[bitbucket:webhook-created] {project}/{repo} #{res.get('id','?')} {name} -> {url}"
458
463
 
459
464
 
460
465
  def _act_delete_webhook(client, project: str, repo: str, kwargs: dict) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.39.1
3
+ Version: 2.40.0
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
@@ -283,9 +283,12 @@ Access Token. Tokens live in the **OS keyring** (Windows Credential Manager,
283
283
  macOS Keychain, Linux Secret Service) — never in `.c3/config.json`.
284
284
 
285
285
  ```bash
286
- # One-time login per server
286
+ # One-time login per server (stored under this project's .c3/config.json)
287
287
  c3 bitbucket login --url https://bitbucket.example.com
288
- # prompts for username + PAT (masked)
288
+ # -> prompts for username + PAT (masked)
289
+
290
+ # ...or store it globally so every C3 project can use it
291
+ c3 bitbucket login --global --url https://bitbucket.example.com
289
292
 
290
293
  # Pin defaults so subsequent calls don't need project/repo
291
294
  c3 bitbucket set-default --project PROJ --repo my-service
@@ -294,6 +297,16 @@ c3 bitbucket set-default --project PROJ --repo my-service
294
297
  c3 bitbucket status
295
298
  ```
296
299
 
300
+ **Account resolution precedence:** the project's `.c3/config.json` wins, but when
301
+ it has no active account C3 falls back to the global `~/.c3/config.json`. So a
302
+ single `login --global` (or any login done from your home directory) is reusable
303
+ across every C3 project — the PAT always lives in the OS keyring, never on disk.
304
+
305
+ > **Upgrading:** stop the running `c3-mcp` server / CLI before `c3 upgrade`. A live
306
+ > process can hold package files open, leaving pip's `~`-prefixed backup dirs
307
+ > (`~ervices`, `~ools`, …) in `site-packages`; those are inert and safe to delete
308
+ > after the upgrade completes.
309
+
297
310
  The MCP tool dispatches by `action`. Read-only actions: `status`, `whoami`,
298
311
  `list_projects`, `list_repos`, `get_repo`, `list_prs`, `get_pr`, `get_pr_diff`,
299
312
  `get_pr_activities`, `list_branches`, `list_commits`, `list_activity`,
@@ -159,6 +159,7 @@ tests/test_activity_reporter.py
159
159
  tests/test_aider_polyglot.py
160
160
  tests/test_bitbucket_cli_smoke.py
161
161
  tests/test_bitbucket_client.py
162
+ tests/test_bitbucket_config_fallback.py
162
163
  tests/test_bitbucket_credentials.py
163
164
  tests/test_bitbucket_tool.py
164
165
  tests/test_c3_shell.py
@@ -290,15 +290,41 @@ BITBUCKET_DEFAULTS = {
290
290
  }
291
291
 
292
292
 
293
+ def _read_bitbucket_section(config_file: Path) -> dict:
294
+ """Return the ``bitbucket`` section of a config file, or ``{}``."""
295
+ if not config_file.exists():
296
+ return {}
297
+ try:
298
+ with open(config_file, encoding="utf-8") as f:
299
+ data = json.load(f)
300
+ except Exception:
301
+ return {}
302
+ section = data.get("bitbucket", {})
303
+ return section if isinstance(section, dict) else {}
304
+
305
+
293
306
  def load_bitbucket_config(project_path: str) -> dict:
294
- """Load Bitbucket config from .c3/config.json, merged with defaults."""
295
- config_file = Path(project_path) / ".c3" / "config.json"
296
- overrides = {}
297
- if config_file.exists():
307
+ """Load Bitbucket config from .c3/config.json, merged with defaults.
308
+
309
+ Resolution precedence: the project ``<project>/.c3/config.json`` wins, but
310
+ when it has no active account we fall back to the global
311
+ ``~/.c3/config.json`` so a one-time ``c3 bitbucket login`` (or
312
+ ``login --global``) is reusable across every C3 project. The PAT itself
313
+ always lives in the OS keyring, never in these files.
314
+ """
315
+ project_file = Path(project_path) / ".c3" / "config.json"
316
+ overrides = _read_bitbucket_section(project_file)
317
+ if not (overrides.get("active") or {}).get("base_url"):
318
+ # Path.home() raises RuntimeError when no home dir is resolvable (e.g.
319
+ # a stripped subprocess env); treat that as "no global fallback".
298
320
  try:
299
- with open(config_file, encoding="utf-8") as f:
300
- data = json.load(f)
301
- overrides = data.get("bitbucket", {})
321
+ home_file = Path.home() / ".c3" / "config.json"
322
+ already_home = home_file.resolve() == project_file.resolve()
302
323
  except Exception:
303
- pass
324
+ home_file = None
325
+ already_home = True
326
+ if home_file is not None and not already_home:
327
+ home_overrides = _read_bitbucket_section(home_file)
328
+ if (home_overrides.get("active") or {}).get("base_url"):
329
+ overrides = home_overrides
304
330
  return {**BITBUCKET_DEFAULTS, **overrides}
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-context-control"
7
- version = "2.39.1"
7
+ version = "2.40.0"
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"
@@ -28,7 +28,15 @@ _TIMEOUT = 30 # seconds
28
28
  _API = "/rest/api/1.0"
29
29
  _BUILD_API = "/rest/build-status/1.0"
30
30
  _BRANCH_UTILS = "/rest/branch-utils/1.0"
31
- _USER_AGENT = "c3-bitbucket/2.30.0"
31
+
32
+ try:
33
+ from importlib.metadata import version as _pkg_version
34
+
35
+ _C3_VERSION = _pkg_version("code-context-control")
36
+ except Exception: # pragma: no cover - package metadata may be unavailable
37
+ _C3_VERSION = "0.0.0"
38
+
39
+ _USER_AGENT = f"c3-bitbucket/{_C3_VERSION}"
32
40
 
33
41
 
34
42
  class BitbucketError(RuntimeError):
@@ -80,6 +88,8 @@ class BitbucketDataCenterClient:
80
88
  params: dict | None = None,
81
89
  api_root: str = _API,
82
90
  raw: bool = False,
91
+ accept: str = "application/json",
92
+ return_headers: bool = False,
83
93
  ) -> Any:
84
94
  full_path = f"{api_root}{path}"
85
95
  if params:
@@ -92,7 +102,7 @@ class BitbucketDataCenterClient:
92
102
 
93
103
  headers = {
94
104
  "Authorization": f"Bearer {self._token}",
95
- "Accept": "application/json",
105
+ "Accept": accept,
96
106
  "User-Agent": _USER_AGENT,
97
107
  }
98
108
  data: bytes | None = None
@@ -109,14 +119,19 @@ class BitbucketDataCenterClient:
109
119
  try:
110
120
  with urllib.request.urlopen(req, timeout=self._timeout, context=self._ssl_context()) as resp:
111
121
  payload = resp.read()
122
+ resp_headers = {k: v for k, v in resp.headers.items()}
112
123
  if raw:
113
- return payload
114
- if not payload:
115
- return {}
116
- try:
117
- return json.loads(payload.decode("utf-8"))
118
- except json.JSONDecodeError:
119
- return {"raw": payload.decode("utf-8", errors="replace")}
124
+ body_out: Any = payload
125
+ elif not payload:
126
+ body_out = {}
127
+ else:
128
+ try:
129
+ body_out = json.loads(payload.decode("utf-8"))
130
+ except json.JSONDecodeError:
131
+ body_out = {"raw": payload.decode("utf-8", errors="replace")}
132
+ if return_headers:
133
+ return body_out, resp_headers
134
+ return body_out
120
135
  except urllib.error.HTTPError as exc:
121
136
  try:
122
137
  err_payload = json.loads(exc.read().decode("utf-8"))
@@ -168,8 +183,27 @@ class BitbucketDataCenterClient:
168
183
  return self._request("GET", "/application-properties")
169
184
 
170
185
  def whoami(self) -> dict:
171
- """The authenticated user (PAT owner)."""
172
- return self._request("GET", "/users/me")
186
+ """The authenticated user (PAT owner).
187
+
188
+ Bitbucket Data Center / Server has no ``/users/me`` endpoint (that is a
189
+ Cloud convention; DC treats ``me`` as a literal username and 404s).
190
+ Resolve the account from the ``X-AUSERNAME`` header that rides on every
191
+ authenticated response, then enrich with the user record when possible.
192
+ """
193
+ _body, headers = self._request(
194
+ "GET", "/application-properties", return_headers=True
195
+ )
196
+ slug = ""
197
+ for key, value in (headers or {}).items():
198
+ if key.lower() == "x-ausername":
199
+ slug = urllib.parse.unquote(value or "")
200
+ break
201
+ if not slug:
202
+ return {}
203
+ try:
204
+ return self._request("GET", f"/users/{urllib.parse.quote(slug)}")
205
+ except BitbucketError:
206
+ return {"name": slug, "slug": slug, "displayName": slug}
173
207
 
174
208
  # ── Projects & repos (read) ───────────────────────────
175
209
 
@@ -282,6 +316,7 @@ class BitbucketDataCenterClient:
282
316
  f"/projects/{project_key}/repos/{repo_slug}/pull-requests/{pr_id}/diff",
283
317
  params={"contextLines": context_lines},
284
318
  raw=True,
319
+ accept="text/plain",
285
320
  )
286
321
  if isinstance(payload, bytes):
287
322
  return payload.decode("utf-8", errors="replace")
@@ -40,7 +40,7 @@ When falling back, state which c3_* tool was attempted and why it was insufficie
40
40
  7. **VALIDATE**: `c3_validate(file_path)` — after edits or before reporting done. Runs deep type check (pyright/tsc) automatically if installed
41
41
  8. **LOG**: `c3_session(action='log')` for decisions. `c3_session(action='snapshot')` before /clear
42
42
  9. **DELEGATE**: `c3_delegate(task, backend='ollama|codex|gemini|claude|auto')` or `c3_agent(workflow=...)` for multi-model pipelines
43
- 10. **BITBUCKET** (when configured, v2.30.0+): `c3_bitbucket(action='...')` — for self-hosted enterprise Bitbucket Data Center / Server: PRs, branches, builds, repo admin. Tokens live in the OS keyring (set up via `c3 bitbucket login`). Read actions are safe in plan mode; write actions (`merge_pr`, `create_branch`, etc.) are auto-logged to the edit ledger.
43
+ 10. **BITBUCKET** (when configured, v2.30.0+): `c3_bitbucket(action='...')` — for self-hosted enterprise Bitbucket Data Center / Server: PRs, branches, builds, repo admin. Tokens live in the OS keyring (set up via `c3 bitbucket login`, or `login --global` for a home config reusable across projects; account resolution precedence is project → home). Read actions are safe in plan mode; write actions (`merge_pr`, `create_branch`, etc.) are auto-logged to the edit ledger.
44
44
  11. **CROSS-PROJECT** (v2.31.0+): `c3_project(action='list|scan|info|search|read|edit|shell|...', project='<name|path>')` — discover and operate on OTHER c3-installed projects. `list`/`scan` need no project; reads (search/read/compress/status/memory/impact/edits/validate/filter) run freely; writes (`edit`, `shell`, memory add/update/delete) require `allow_write=true` and are logged to the target project's ledger.
45
45
 
46
46
  ## Plan mode