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.
- {code_context_control-2.34.0 → code_context_control-2.35.0}/PKG-INFO +2 -2
- {code_context_control-2.34.0 → code_context_control-2.35.0}/README.md +1 -1
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/c3.py +1 -1
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/mcp_server.py +4 -3
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/edits.py +7 -2
- {code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/PKG-INFO +2 -2
- {code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/SOURCES.txt +2 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/pyproject.toml +1 -1
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/agents.py +145 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/context_snapshot.py +37 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/edit_ledger.py +24 -26
- code_context_control-2.35.0/services/git_context.py +243 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/session_manager.py +17 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/version_tracker.py +8 -16
- code_context_control-2.35.0/tests/test_git_branch_awareness.py +162 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/LICENSE +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/__init__.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/_hook_utils.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/commands/__init__.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/commands/common.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/commands/parser.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/docs.html +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/edits.html +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_auto_snapshot.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_c3_signal.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_c3read.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_edit_ledger.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_edit_unlock.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_filter.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_ghost_files.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_pretool_enforce.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_read.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_session_stats.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hook_terse_advisor.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hub.html +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/hub_server.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/mcp_proxy.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/server.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/__init__.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/_helpers.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/agent.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/bitbucket.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/compress.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/delegate.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/edit.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/filter.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/impact.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/memory.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/project.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/read.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/search.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/session.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/shell.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/status.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/tools/validate.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/api.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/app.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/bitbucket.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/chat.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/dashboard.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/edits.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/instructions.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/memory.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/sessions.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/settings.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/components/sidebar.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/icons.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/shared.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui/theme.js +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui.html +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui_legacy.html +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/cli/ui_nano.html +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/dependency_links.txt +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/entry_points.txt +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/requires.txt +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/top_level.txt +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/core/__init__.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/core/config.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/core/ide.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/core/mcp_toml.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/core/web_security.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/__init__.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/config.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/mcp_oracle.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/oracle.html +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/oracle_server.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/__init__.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/api_auth.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/c3_bridge.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/chat_engine.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/chat_store.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/cross_memory.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/federated_graph.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/health_checker.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/insight_engine.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/memory_reader.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/memory_writer.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/ollama_bridge.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/project_scanner.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/review_agent.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/tool_executor.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/oracle/services/tool_registry.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/__init__.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/activity_log.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/agent_base.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/auto_memory.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/bench/__init__.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/bench/external/__init__.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/bench/external/aider_polyglot.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/bench/external/swe_bench.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/benchmark_dashboard.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/bitbucket_client.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/bitbucket_credentials.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/claude_md.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/compressor.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/conversation_store.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/doc_index.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/e2e_benchmark.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/e2e_evaluator.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/e2e_tasks.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/embedding_index.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/error_reporting.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/file_memory.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/hub_service.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/indexer.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/memory.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/memory_consolidator.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/memory_graph.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/memory_grounder.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/memory_scorer.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/metrics.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/notifications.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/ollama_client.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/output_filter.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/parser.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/project_manager.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/project_runtime.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/protocol.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/proxy_state.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/retrieval_broker.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/router.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/runtime.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/session_benchmark.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/session_preloader.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/text_index.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/tool_classifier.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/transcript_index.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/validation_cache.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/vector_store.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/services/watcher.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/setup.cfg +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_aider_polyglot.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_bitbucket_cli_smoke.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_bitbucket_client.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_bitbucket_credentials.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_bitbucket_tool.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_c3_shell.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_cli_smoke.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_e2e_benchmark.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_edit_normalization.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_enforcement_flip.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_federated_graph.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_ghost_files.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_hub_server_smoke.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_mcp_host_guard.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_mcp_server_smoke.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_mcp_toml.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_memory_graph_api.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_memory_system.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_notification_discipline.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_oracle_api_auth.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_oracle_apikey_api.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_oracle_discovery_api.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_output_filter.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_permissions.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_project_manager.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_project_manager_merge.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_project_tool.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_read_coercion.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_session_benchmark.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_session_budget.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_shell_robustness.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_swe_bench.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_tool_registry.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_validate.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_web_security.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tests/test_windows_reliability.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/__init__.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/backend.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/main.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/__init__.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/benchmark_view.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/claudemd_view.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/compress_view.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/index_view.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/init_view.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/mcp_view.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/optimize_view.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/pipe_view.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/projects_view.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/search_view.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/session_view.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/stats.py +0 -0
- {code_context_control-2.34.0 → code_context_control-2.35.0}/tui/screens/ui_view.py +0 -0
- {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.
|
|
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
|
|
|
@@ -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
|
-
|
|
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)
|
{code_context_control-2.34.0 → code_context_control-2.35.0}/code_context_control.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-context-control
|
|
3
|
-
Version: 2.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|