code-context-control 2.34.0__tar.gz → 2.35.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 (205) hide show
  1. {code_context_control-2.34.0 → code_context_control-2.35.0}/PKG-INFO +2 -2
  2. {code_context_control-2.34.0 → code_context_control-2.35.0}/README.md +1 -1
  3. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/c3.py +1 -1
  4. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/mcp_server.py +4 -3
  5. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/edits.py +7 -2
  6. {code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/PKG-INFO +2 -2
  7. {code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/SOURCES.txt +2 -0
  8. {code_context_control-2.34.0 → code_context_control-2.35.0}/pyproject.toml +1 -1
  9. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/agents.py +145 -0
  10. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/context_snapshot.py +37 -0
  11. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/edit_ledger.py +24 -26
  12. code_context_control-2.35.0/services/git_context.py +243 -0
  13. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/session_manager.py +17 -0
  14. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/version_tracker.py +8 -16
  15. code_context_control-2.35.0/tests/test_git_branch_awareness.py +162 -0
  16. {code_context_control-2.34.0 → code_context_control-2.35.0}/LICENSE +0 -0
  17. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/__init__.py +0 -0
  18. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/_hook_utils.py +0 -0
  19. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/commands/__init__.py +0 -0
  20. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/commands/common.py +0 -0
  21. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/commands/parser.py +0 -0
  22. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/docs.html +0 -0
  23. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/edits.html +0 -0
  24. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_auto_snapshot.py +0 -0
  25. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_c3_signal.py +0 -0
  26. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_c3read.py +0 -0
  27. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_edit_ledger.py +0 -0
  28. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_edit_unlock.py +0 -0
  29. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_filter.py +0 -0
  30. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_ghost_files.py +0 -0
  31. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_pretool_enforce.py +0 -0
  32. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_read.py +0 -0
  33. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_session_stats.py +0 -0
  34. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_terse_advisor.py +0 -0
  35. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hub.html +0 -0
  36. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hub_server.py +0 -0
  37. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/mcp_proxy.py +0 -0
  38. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/server.py +0 -0
  39. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/__init__.py +0 -0
  40. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/_helpers.py +0 -0
  41. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/agent.py +0 -0
  42. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/bitbucket.py +0 -0
  43. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/compress.py +0 -0
  44. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/delegate.py +0 -0
  45. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/edit.py +0 -0
  46. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/filter.py +0 -0
  47. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/impact.py +0 -0
  48. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/memory.py +0 -0
  49. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/project.py +0 -0
  50. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/read.py +0 -0
  51. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/search.py +0 -0
  52. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/session.py +0 -0
  53. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/shell.py +0 -0
  54. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/status.py +0 -0
  55. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/validate.py +0 -0
  56. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/api.js +0 -0
  57. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/app.js +0 -0
  58. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/bitbucket.js +0 -0
  59. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/chat.js +0 -0
  60. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/dashboard.js +0 -0
  61. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/edits.js +0 -0
  62. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/instructions.js +0 -0
  63. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/memory.js +0 -0
  64. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/sessions.js +0 -0
  65. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/settings.js +0 -0
  66. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/sidebar.js +0 -0
  67. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/icons.js +0 -0
  68. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/shared.js +0 -0
  69. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/theme.js +0 -0
  70. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui.html +0 -0
  71. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui_legacy.html +0 -0
  72. {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui_nano.html +0 -0
  73. {code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/dependency_links.txt +0 -0
  74. {code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/entry_points.txt +0 -0
  75. {code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/requires.txt +0 -0
  76. {code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/top_level.txt +0 -0
  77. {code_context_control-2.34.0 → code_context_control-2.35.0}/core/__init__.py +0 -0
  78. {code_context_control-2.34.0 → code_context_control-2.35.0}/core/config.py +0 -0
  79. {code_context_control-2.34.0 → code_context_control-2.35.0}/core/ide.py +0 -0
  80. {code_context_control-2.34.0 → code_context_control-2.35.0}/core/mcp_toml.py +0 -0
  81. {code_context_control-2.34.0 → code_context_control-2.35.0}/core/web_security.py +0 -0
  82. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/__init__.py +0 -0
  83. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/config.py +0 -0
  84. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/mcp_oracle.py +0 -0
  85. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/oracle.html +0 -0
  86. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/oracle_server.py +0 -0
  87. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/__init__.py +0 -0
  88. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/api_auth.py +0 -0
  89. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/c3_bridge.py +0 -0
  90. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/chat_engine.py +0 -0
  91. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/chat_store.py +0 -0
  92. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/cross_memory.py +0 -0
  93. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/federated_graph.py +0 -0
  94. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/health_checker.py +0 -0
  95. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/insight_engine.py +0 -0
  96. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/memory_reader.py +0 -0
  97. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/memory_writer.py +0 -0
  98. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/ollama_bridge.py +0 -0
  99. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/project_scanner.py +0 -0
  100. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/review_agent.py +0 -0
  101. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/tool_executor.py +0 -0
  102. {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/tool_registry.py +0 -0
  103. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/__init__.py +0 -0
  104. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/activity_log.py +0 -0
  105. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/agent_base.py +0 -0
  106. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/auto_memory.py +0 -0
  107. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/bench/__init__.py +0 -0
  108. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/bench/external/__init__.py +0 -0
  109. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/bench/external/aider_polyglot.py +0 -0
  110. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/bench/external/swe_bench.py +0 -0
  111. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/benchmark_dashboard.py +0 -0
  112. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/bitbucket_client.py +0 -0
  113. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/bitbucket_credentials.py +0 -0
  114. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/claude_md.py +0 -0
  115. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/compressor.py +0 -0
  116. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/conversation_store.py +0 -0
  117. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/doc_index.py +0 -0
  118. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/e2e_benchmark.py +0 -0
  119. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/e2e_evaluator.py +0 -0
  120. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/e2e_tasks.py +0 -0
  121. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/embedding_index.py +0 -0
  122. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/error_reporting.py +0 -0
  123. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/file_memory.py +0 -0
  124. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/hub_service.py +0 -0
  125. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/indexer.py +0 -0
  126. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/memory.py +0 -0
  127. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/memory_consolidator.py +0 -0
  128. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/memory_graph.py +0 -0
  129. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/memory_grounder.py +0 -0
  130. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/memory_scorer.py +0 -0
  131. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/metrics.py +0 -0
  132. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/notifications.py +0 -0
  133. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/ollama_client.py +0 -0
  134. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/output_filter.py +0 -0
  135. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/parser.py +0 -0
  136. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/project_manager.py +0 -0
  137. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/project_runtime.py +0 -0
  138. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/protocol.py +0 -0
  139. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/proxy_state.py +0 -0
  140. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/retrieval_broker.py +0 -0
  141. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/router.py +0 -0
  142. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/runtime.py +0 -0
  143. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/session_benchmark.py +0 -0
  144. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/session_preloader.py +0 -0
  145. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/text_index.py +0 -0
  146. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/tool_classifier.py +0 -0
  147. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/transcript_index.py +0 -0
  148. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/validation_cache.py +0 -0
  149. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/vector_store.py +0 -0
  150. {code_context_control-2.34.0 → code_context_control-2.35.0}/services/watcher.py +0 -0
  151. {code_context_control-2.34.0 → code_context_control-2.35.0}/setup.cfg +0 -0
  152. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_aider_polyglot.py +0 -0
  153. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_bitbucket_cli_smoke.py +0 -0
  154. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_bitbucket_client.py +0 -0
  155. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_bitbucket_credentials.py +0 -0
  156. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_bitbucket_tool.py +0 -0
  157. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_c3_shell.py +0 -0
  158. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_cli_smoke.py +0 -0
  159. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_e2e_benchmark.py +0 -0
  160. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_edit_normalization.py +0 -0
  161. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_enforcement_flip.py +0 -0
  162. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_federated_graph.py +0 -0
  163. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_ghost_files.py +0 -0
  164. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_hub_server_smoke.py +0 -0
  165. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_mcp_host_guard.py +0 -0
  166. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_mcp_server_smoke.py +0 -0
  167. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_mcp_toml.py +0 -0
  168. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_memory_graph_api.py +0 -0
  169. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_memory_system.py +0 -0
  170. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_notification_discipline.py +0 -0
  171. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_oracle_api_auth.py +0 -0
  172. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_oracle_apikey_api.py +0 -0
  173. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_oracle_discovery_api.py +0 -0
  174. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_output_filter.py +0 -0
  175. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_permissions.py +0 -0
  176. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_project_manager.py +0 -0
  177. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_project_manager_merge.py +0 -0
  178. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_project_tool.py +0 -0
  179. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_read_coercion.py +0 -0
  180. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_session_benchmark.py +0 -0
  181. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_session_budget.py +0 -0
  182. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_shell_robustness.py +0 -0
  183. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_swe_bench.py +0 -0
  184. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_tool_registry.py +0 -0
  185. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_validate.py +0 -0
  186. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_web_security.py +0 -0
  187. {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_windows_reliability.py +0 -0
  188. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/__init__.py +0 -0
  189. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/backend.py +0 -0
  190. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/main.py +0 -0
  191. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/__init__.py +0 -0
  192. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/benchmark_view.py +0 -0
  193. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/claudemd_view.py +0 -0
  194. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/compress_view.py +0 -0
  195. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/index_view.py +0 -0
  196. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/init_view.py +0 -0
  197. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/mcp_view.py +0 -0
  198. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/optimize_view.py +0 -0
  199. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/pipe_view.py +0 -0
  200. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/projects_view.py +0 -0
  201. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/search_view.py +0 -0
  202. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/session_view.py +0 -0
  203. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/stats.py +0 -0
  204. {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/ui_view.py +0 -0
  205. {code_context_control-2.34.0 → code_context_control-2.35.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.34.0
3
+ Version: 2.35.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
@@ -245,7 +245,7 @@ C3 exposes 16 tools as a native MCP server. Your IDE calls them directly:
245
245
  | `c3_impact` | Blast-radius analysis before edits to shared symbols |
246
246
  | `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini / etc. |
247
247
  | `c3_agent` | Multi-step agentic workflows (review, investigate, refactor) |
248
- | `c3_edits` | Edit-ledger queries + version diffs + restore points |
248
+ | `c3_edits` | Edit-ledger queries + version diffs + restore points + per-branch filter |
249
249
  | `c3_bitbucket` | Bitbucket Data Center integration — PRs, branches, builds, repo admin (v2.30.0) |
250
250
  | `c3_project` | Cross-project — discover & operate on other c3-installed projects; guarded writes (v2.31.0) |
251
251
 
@@ -183,7 +183,7 @@ C3 exposes 16 tools as a native MCP server. Your IDE calls them directly:
183
183
  | `c3_impact` | Blast-radius analysis before edits to shared symbols |
184
184
  | `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini / etc. |
185
185
  | `c3_agent` | Multi-step agentic workflows (review, investigate, refactor) |
186
- | `c3_edits` | Edit-ledger queries + version diffs + restore points |
186
+ | `c3_edits` | Edit-ledger queries + version diffs + restore points + per-branch filter |
187
187
  | `c3_bitbucket` | Bitbucket Data Center integration — PRs, branches, builds, repo admin (v2.30.0) |
188
188
  | `c3_project` | Cross-project — discover & operate on other c3-installed projects; guarded writes (v2.31.0) |
189
189
 
@@ -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.34.0"
88
+ __version__ = "2.35.0"
89
89
 
90
90
 
91
91
  def _command_deps() -> CommandDeps:
@@ -604,9 +604,10 @@ async def c3_edit(file_path: str, old_string: str = "", new_string: str = "",
604
604
  async def c3_edits(action: str, file: str = "", change_type: str = "modified",
605
605
  summary: str = "", lines_changed: str = "", tags: str = "",
606
606
  limit: int = 50, since: str = "", edit_id: str = "",
607
- tag: str = "", ctx: Context = None) -> str:
607
+ tag: str = "", branch: str = "", ctx: Context = None) -> str:
608
608
  """EDIT HISTORY — inspect the ledger. Different from c3_edit (which writes); this one reads.
609
- actions: log (append entry), history (recent edits), versions (per-file), stats, tag (mark edit_id)."""
609
+ actions: log (append entry), history (recent edits), versions (per-file), stats, tag (mark edit_id).
610
+ branch: filter history to edits stamped with a given git branch."""
610
611
  svc = _svc(ctx)
611
612
 
612
613
  def finalize(name, args, resp, summ, **kw):
@@ -615,7 +616,7 @@ async def c3_edits(action: str, file: str = "", change_type: str = "modified",
615
616
  from cli.tools.edits import handle_edits
616
617
  return await asyncio.to_thread(handle_edits, action, file, change_type, summary,
617
618
  lines_changed, tags, limit, since, edit_id, tag,
618
- svc, finalize)
619
+ svc, finalize, branch)
619
620
 
620
621
 
621
622
  @mcp.tool()
@@ -3,7 +3,7 @@
3
3
 
4
4
  def handle_edits(action: str, file: str, change_type: str, summary: str,
5
5
  lines_changed: str, tags: str, limit: int, since: str,
6
- edit_id: str, tag: str, svc, finalize) -> str:
6
+ edit_id: str, tag: str, svc, finalize, branch: str = "") -> str:
7
7
  """Route c3_edits actions."""
8
8
  ledger = svc.edit_ledger
9
9
  if ledger is None:
@@ -64,12 +64,17 @@ def handle_edits(action: str, file: str, change_type: str, summary: str,
64
64
  file=file or None,
65
65
  limit=limit or 50,
66
66
  since=since or None,
67
+ branch=branch or None,
67
68
  )
68
69
  if not entries:
69
70
  return finalize("c3_edits", {"action": "history"}, "No edits found", "0 edits")
70
- lines = [f"[edits:history] {len(entries)} entries" + (f" for {file}" if file else "")]
71
+ scope = (f" for {file}" if file else "") + (f" on {branch}" if branch else "")
72
+ lines = [f"[edits:history] {len(entries)} entries" + scope]
71
73
  for e in entries:
72
74
  ln = f" {e['timestamp'][:19]} | {e['file']} {e['version']} | {e['change_type']} | {e['summary']}"
75
+ br = (e.get("git") or {}).get("branch")
76
+ if br:
77
+ ln += f" @{br}"
73
78
  if e.get("tags"):
74
79
  ln += f" [{','.join(e['tags'])}]"
75
80
  lines.append(ln)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.34.0
3
+ Version: 2.35.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
@@ -245,7 +245,7 @@ C3 exposes 16 tools as a native MCP server. Your IDE calls them directly:
245
245
  | `c3_impact` | Blast-radius analysis before edits to shared symbols |
246
246
  | `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini / etc. |
247
247
  | `c3_agent` | Multi-step agentic workflows (review, investigate, refactor) |
248
- | `c3_edits` | Edit-ledger queries + version diffs + restore points |
248
+ | `c3_edits` | Edit-ledger queries + version diffs + restore points + per-branch filter |
249
249
  | `c3_bitbucket` | Bitbucket Data Center integration — PRs, branches, builds, repo admin (v2.30.0) |
250
250
  | `c3_project` | Cross-project — discover & operate on other c3-installed projects; guarded writes (v2.31.0) |
251
251
 
@@ -112,6 +112,7 @@ services/edit_ledger.py
112
112
  services/embedding_index.py
113
113
  services/error_reporting.py
114
114
  services/file_memory.py
115
+ services/git_context.py
115
116
  services/hub_service.py
116
117
  services/indexer.py
117
118
  services/memory.py
@@ -157,6 +158,7 @@ tests/test_edit_normalization.py
157
158
  tests/test_enforcement_flip.py
158
159
  tests/test_federated_graph.py
159
160
  tests/test_ghost_files.py
161
+ tests/test_git_branch_awareness.py
160
162
  tests/test_hub_server_smoke.py
161
163
  tests/test_mcp_host_guard.py
162
164
  tests/test_mcp_server_smoke.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-context-control"
7
- version = "2.34.0"
7
+ version = "2.35.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"
@@ -11,6 +11,7 @@ from collections import Counter
11
11
  from pathlib import Path
12
12
 
13
13
  from services.agent_base import BackgroundAgent # noqa: F401 — re-exported for consumers
14
+ from services.git_context import GitContext
14
15
 
15
16
 
16
17
  class IndexStalenessAgent(BackgroundAgent):
@@ -922,6 +923,137 @@ class FileMemoryAgent(BackgroundAgent):
922
923
  return status
923
924
 
924
925
 
926
+ class BranchWatchAgent(BackgroundAgent):
927
+ """Detects branch / HEAD changes and queues a scoped, targeted re-index.
928
+
929
+ Change detection keys on the HEAD sha, so it fires on checkout, switch,
930
+ pull, and merge — but NOT on ``git fetch`` (which only moves remote refs;
931
+ the working tree is untouched). On a move it queues exactly the files that
932
+ differ between the old and new HEAD (from ``git diff``), restricted to files
933
+ C3 already tracks and that genuinely need re-indexing, then notifies
934
+ (warning on a branch switch, info on a same-branch HEAD move).
935
+
936
+ Every cycle it also queues tracked files that are dirty on disk, catching
937
+ edits made outside C3 (rebase, ``git restore``, another editor) that the
938
+ lazy mtime-on-access path would only notice later. The actual re-extraction
939
+ is done by FileMemoryAgent, which drains the same queue.
940
+ """
941
+
942
+ def __init__(self, file_memory, notifications, project_path,
943
+ enabled=True, interval=30, max_queue=200, **kwargs):
944
+ super().__init__("BranchWatch", interval, notifications, enabled, **kwargs)
945
+ self.file_memory = file_memory
946
+ self.project_path = project_path
947
+ self.max_queue = max_queue
948
+ self._git = GitContext(project_path)
949
+ self._state_path = Path(project_path) / ".c3" / "branch_state.json"
950
+ self._loaded = False
951
+ self._last = {"branch": None, "head_sha": ""}
952
+
953
+ def _load_state(self):
954
+ if self._loaded:
955
+ return
956
+ self._loaded = True
957
+ try:
958
+ if self._state_path.exists():
959
+ data = json.loads(self._state_path.read_text(encoding="utf-8"))
960
+ self._last = {"branch": data.get("branch"),
961
+ "head_sha": data.get("head_sha", "")}
962
+ except Exception:
963
+ pass
964
+
965
+ def _save_state(self, branch, head_sha):
966
+ self._last = {"branch": branch, "head_sha": head_sha}
967
+ try:
968
+ self._state_path.write_text(
969
+ json.dumps({"branch": branch, "head_sha": head_sha}),
970
+ encoding="utf-8",
971
+ )
972
+ except Exception:
973
+ pass
974
+
975
+ def _queue_changed(self, paths) -> int:
976
+ """Queue tracked files that still need re-indexing; return count queued.
977
+
978
+ Paths are compared slash-agnostically against the file-memory store and
979
+ filtered through ``needs_update`` so unchanged files (e.g. after a local
980
+ commit where the working tree already matches) are not re-processed.
981
+ """
982
+ if not paths:
983
+ return 0
984
+ tracked = {t.replace("\\", "/"): t for t in self.file_memory.list_tracked()}
985
+ queued = 0
986
+ for p in paths:
987
+ canonical = tracked.get(p.replace("\\", "/"))
988
+ if canonical is None:
989
+ continue
990
+ try:
991
+ if not self.file_memory.needs_update(canonical):
992
+ continue
993
+ except Exception:
994
+ pass
995
+ self.file_memory.queue_for_update(canonical)
996
+ queued += 1
997
+ if queued >= self.max_queue:
998
+ break
999
+ return queued
1000
+
1001
+ def check(self):
1002
+ st = self._git.state(force=True)
1003
+ if not st.get("available"):
1004
+ return False # no git here — idle backoff
1005
+ self._load_state()
1006
+ cur_branch = st.get("branch")
1007
+ cur_head = st.get("head_sha", "")
1008
+ if not cur_head:
1009
+ return False
1010
+ prev_branch = self._last.get("branch")
1011
+ prev_head = self._last.get("head_sha", "")
1012
+
1013
+ # Catch out-of-band working-tree edits every cycle.
1014
+ dirty_queued = self._queue_changed(self._git.dirty_files())
1015
+
1016
+ # First run for this project — record the baseline without notifying.
1017
+ if not prev_head:
1018
+ self._save_state(cur_branch, cur_head)
1019
+ return False if dirty_queued == 0 else None
1020
+
1021
+ if cur_head == prev_head and cur_branch == prev_branch:
1022
+ return False if dirty_queued == 0 else None
1023
+
1024
+ # HEAD or branch moved — scope the re-index to what actually differs.
1025
+ changed = self._git.changed_files(prev_head, cur_head)
1026
+ if not changed:
1027
+ # Old commit unreachable / diff failed — fall back to dirty set.
1028
+ changed = self._git.dirty_files()
1029
+ queued = self._queue_changed(changed)
1030
+
1031
+ switched = (cur_branch != prev_branch)
1032
+ self._save_state(cur_branch, cur_head)
1033
+
1034
+ new_label = cur_branch or "(detached)"
1035
+ if switched:
1036
+ old_label = prev_branch or (prev_head[:8] if prev_head else "?")
1037
+ self.notify(
1038
+ "warning", "Branch changed",
1039
+ f"{old_label} → {new_label}; queued {queued} tracked file(s) for re-index",
1040
+ replace_if_unacked=True,
1041
+ )
1042
+ else:
1043
+ self.notify(
1044
+ "info", "Index refresh",
1045
+ f"HEAD {prev_head[:8]}→{cur_head[:8]} on {new_label}; "
1046
+ f"queued {queued} file(s) for re-index",
1047
+ replace_if_unacked=True,
1048
+ )
1049
+ return None
1050
+
1051
+ def get_status(self) -> dict:
1052
+ status = super().get_status()
1053
+ status["branch"] = self._git.label()
1054
+ return status
1055
+
1056
+
925
1057
  class AutonomyPlannerAgent(BackgroundAgent):
926
1058
  """Builds a prioritized autonomous action plan from recent tool telemetry."""
927
1059
 
@@ -1511,6 +1643,19 @@ def create_agents(services, notifications, config=None, ollama=None) -> list:
1511
1643
  )
1512
1644
  )
1513
1645
 
1646
+ # BranchWatchAgent — needs file_memory's re-index queue.
1647
+ if hasattr(services, 'file_memory') and services.file_memory:
1648
+ agents.append(
1649
+ BranchWatchAgent(
1650
+ file_memory=services.file_memory,
1651
+ notifications=notifications,
1652
+ project_path=getattr(services, 'project_path', None),
1653
+ **_cfg("BranchWatch", {
1654
+ "enabled": True, "interval": 30, "max_queue": 200,
1655
+ }),
1656
+ )
1657
+ )
1658
+
1514
1659
  # EditLedgerEnricherAgent — only if edit_ledger is available
1515
1660
  if getattr(services, 'edit_ledger', None):
1516
1661
  agents.append(
@@ -9,6 +9,7 @@ from datetime import datetime, timezone
9
9
  from pathlib import Path
10
10
 
11
11
  from core import count_tokens
12
+ from services.git_context import GitContext
12
13
 
13
14
  # Max characters per file structural map stored in snapshot
14
15
  _FILE_MAP_MAX_CHARS = 600
@@ -21,6 +22,7 @@ class ContextSnapshot:
21
22
  self.project_path = Path(project_path)
22
23
  self.data_dir = self.project_path / data_dir
23
24
  self.data_dir.mkdir(parents=True, exist_ok=True)
25
+ self._git = GitContext(self.project_path)
24
26
 
25
27
  def capture(self, session_mgr, memory_store,
26
28
  task_description: str = "",
@@ -88,11 +90,23 @@ class ContextSnapshot:
88
90
  # Context budget snapshot
89
91
  budget = session.get("context_budget", {})
90
92
 
93
+ # Git working-tree state — lets restore flag a branch change.
94
+ try:
95
+ gstate = self._git.state(force=True)
96
+ git_info = {
97
+ "branch": gstate.get("branch"),
98
+ "head_sha": gstate.get("head_sha", ""),
99
+ "detached": gstate.get("detached", False),
100
+ }
101
+ except Exception:
102
+ git_info = {"branch": None, "head_sha": "", "detached": False}
103
+
91
104
  snapshot = {
92
105
  "schema_version": 3,
93
106
  "snapshot_id": datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S"),
94
107
  "created": datetime.now(timezone.utc).isoformat(),
95
108
  "session_id": session_id,
109
+ "git": git_info,
96
110
  "task_description": task_description,
97
111
  "working_files": working_files or [],
98
112
  "custom_notes": custom_notes,
@@ -152,6 +166,20 @@ class ContextSnapshot:
152
166
  if "error" in snap:
153
167
  return snap
154
168
 
169
+ # Flag when the working tree has moved off the branch this was taken on.
170
+ snap_git = snap.get("git") or {}
171
+ if snap_git.get("branch"):
172
+ try:
173
+ cur = self._git.state(force=True)
174
+ if (cur.get("available") and cur.get("branch")
175
+ and cur["branch"] != snap_git["branch"]):
176
+ snap["_branch_warning"] = (
177
+ f"snapshot taken on '{snap_git['branch']}', "
178
+ f"now on '{cur['branch']}'"
179
+ )
180
+ except Exception:
181
+ pass
182
+
155
183
  # Enrich with live memory recall so cross-session facts are surfaced immediately
156
184
  if memory_store and snap.get("task_description"):
157
185
  try:
@@ -252,6 +280,13 @@ class ContextSnapshot:
252
280
  parts = [f"# Context Restore: {snap.get('task_description', 'N/A')}"]
253
281
  parts.append(f"Snapshot: {snap['snapshot_id']} | Session: {snap.get('session_id', '?')}")
254
282
 
283
+ git = snap.get("git") or {}
284
+ if git.get("head_sha"):
285
+ label = (git.get("branch") or "(detached)") + " @ " + git["head_sha"][:8]
286
+ parts.append(f"Branch: {label}")
287
+ if snap.get("_branch_warning"):
288
+ parts.append(f"\n⚠️ Branch changed since snapshot — {snap['_branch_warning']}")
289
+
255
290
  if snap.get("custom_notes"):
256
291
  parts.append(f"\n## Notes\n{snap['custom_notes']}")
257
292
 
@@ -328,6 +363,8 @@ class ContextSnapshot:
328
363
  def _compact_briefing(self, snap: dict) -> str:
329
364
  """Level 1: Compact briefing — top decisions + file list."""
330
365
  parts = [f"[restore:{snap['snapshot_id']}] {snap.get('task_description', '')}"]
366
+ if snap.get("_branch_warning"):
367
+ parts.append(f"⚠️ branch changed — {snap['_branch_warning']}")
331
368
 
332
369
  plans = snap.get("plans", [])
333
370
  if plans:
@@ -15,6 +15,8 @@ from collections import Counter
15
15
  from datetime import datetime, timezone
16
16
  from pathlib import Path
17
17
 
18
+ from services.git_context import GitContext
19
+
18
20
 
19
21
  class EditLedger:
20
22
  """Tracks every AI edit with version numbering and git context."""
@@ -23,7 +25,8 @@ class EditLedger:
23
25
  self.project_path = Path(project_path).resolve()
24
26
  self.ledger_file = self.project_path / ".c3" / "edit_ledger.jsonl"
25
27
  self.ledger_file.parent.mkdir(parents=True, exist_ok=True)
26
- self._git_root = self._detect_git_root()
28
+ self._git = GitContext(self.project_path)
29
+ self._git_root = self._git.git_root
27
30
  # In-memory caches — loaded lazily on first use, updated on writes
28
31
  self._version_cache: dict[str, int] | None = None # {file: max_version}
29
32
  self._total_count: int | None = None
@@ -75,7 +78,8 @@ class EditLedger:
75
78
  self._seq_counter += 1
76
79
 
77
80
  # Git info — single combined command when enabled
78
- git_info = {"commit": "", "author": "", "subject": "", "dirty": False}
81
+ git_info = {"commit": "", "author": "", "subject": "", "dirty": False,
82
+ "branch": None, "head_sha": ""}
79
83
  diff_summary = ""
80
84
  if include_git and self._git_root:
81
85
  git_info, diff_summary = self._git_combined(rel)
@@ -107,9 +111,10 @@ class EditLedger:
107
111
  return entry
108
112
 
109
113
  def get_history(self, file: str = None, limit: int = 50,
110
- since: str = None) -> list:
111
- """Query edits, optionally filtered by file and/or time."""
112
- results = self._load_merged(file_filter=file, since_filter=since)
114
+ since: str = None, branch: str = None) -> list:
115
+ """Query edits, optionally filtered by file, time, and/or branch."""
116
+ results = self._load_merged(file_filter=file, since_filter=since,
117
+ branch_filter=branch)
113
118
  return results[-limit:]
114
119
 
115
120
  def get_file_versions(self, file: str) -> list:
@@ -208,31 +213,13 @@ class EditLedger:
208
213
  continue
209
214
  return results[-limit:]
210
215
 
211
- def _detect_git_root(self):
212
- """Find git root directory."""
213
- try:
214
- kwargs = {}
215
- if sys.platform == "win32":
216
- kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
217
- result = subprocess.run(
218
- ["git", "rev-parse", "--show-toplevel"],
219
- cwd=self.project_path,
220
- capture_output=True, text=True, timeout=3,
221
- stdin=subprocess.DEVNULL,
222
- **kwargs,
223
- )
224
- if result.returncode == 0:
225
- return Path(result.stdout.strip()).resolve()
226
- except Exception:
227
- pass
228
- return None
229
-
230
216
  def _git_combined(self, rel_path: str) -> tuple:
231
217
  """Capture git info + diff in a single subprocess call.
232
218
 
233
219
  Returns (git_info_dict, diff_summary_str).
234
220
  """
235
- info = {"commit": "", "author": "", "subject": "", "dirty": False}
221
+ info = {"commit": "", "author": "", "subject": "", "dirty": False,
222
+ "branch": None, "head_sha": ""}
236
223
  diff_summary = ""
237
224
  abs_path = (self.project_path / rel_path).resolve()
238
225
  try:
@@ -296,11 +283,20 @@ class EditLedger:
296
283
  except Exception:
297
284
  pass
298
285
 
286
+ # Branch + HEAD from the cached GitContext (cheap; shared TTL cache).
287
+ try:
288
+ gstate = self._git.state()
289
+ info["branch"] = gstate.get("branch")
290
+ info["head_sha"] = gstate.get("head_sha", "")
291
+ except Exception:
292
+ pass
293
+
299
294
  return info, diff_summary
300
295
 
301
296
  # ── Async enrichment ──────────────────────────────────────────────
302
297
 
303
- def _load_merged(self, file_filter: str = None, since_filter: str = None) -> list:
298
+ def _load_merged(self, file_filter: str = None, since_filter: str = None,
299
+ branch_filter: str = None) -> list:
304
300
  """Read all base entries with any appended patches merged in.
305
301
 
306
302
  Patch entries are identified by having a 'target_id' field.
@@ -348,6 +344,8 @@ class EditLedger:
348
344
  continue
349
345
  if since_filter and entry.get("timestamp", "") < since_filter:
350
346
  continue
347
+ if branch_filter and (entry.get("git") or {}).get("branch") != branch_filter:
348
+ continue
351
349
  results.append(entry)
352
350
  results.sort(key=lambda e: e.get("timestamp", ""))
353
351
  return results