code-context-control 2.92.0__tar.gz → 2.92.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {code_context_control-2.92.0/code_context_control.egg-info → code_context_control-2.92.1}/PKG-INFO +1 -1
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/_hook_utils.py +38 -0
- code_context_control-2.92.1/cli/_shell_writes.py +120 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/c3.py +1 -1
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_c3_signal.py +5 -1
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_dispatch.py +4 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_edit_ledger.py +86 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_edit_unlock.py +8 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_pretool_enforce.py +69 -1
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/mcp_server.py +9 -1
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/compress.py +2 -1
- {code_context_control-2.92.0 → code_context_control-2.92.1/code_context_control.egg-info}/PKG-INFO +1 -1
- {code_context_control-2.92.0 → code_context_control-2.92.1}/code_context_control.egg-info/SOURCES.txt +4 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/pyproject.toml +1 -1
- code_context_control-2.92.1/tests/test_failed_calls_do_not_unlock.py +124 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hook_codex_compat.py +2 -2
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hook_dispatch.py +4 -1
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hook_pretool_enforce.py +52 -0
- code_context_control-2.92.1/tests/test_shell_write_ledger.py +111 -0
- code_context_control-2.92.1/tests/test_shell_writes.py +84 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/LICENSE +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/README.md +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/__init__.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/commands/__init__.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/commands/common.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/commands/parser.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/docs.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/edits.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/guide/access.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/guide/bitbucket.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/guide/credentials.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/guide/getting-started.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/guide/index.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/guide/masking.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/guide/oracle.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/guide/shared.css +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/guide/tools.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/guide/workflow.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_access_guard.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_artifact.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_auto_snapshot.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_c3read.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_filter.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_ghost_files.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_prompt_recall.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_read.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_session_stats.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hook_terse_advisor.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_server.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/app.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/add_project.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/config_editor.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/drill_artifacts.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/drill_health.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/drill_panel.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/drill_subprojects.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/drill_tasks.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/drill_views.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/global_search.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/hub_ci.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/hub_credentials.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/hub_enforcement.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/hub_locks.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/mcp_manager.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/modals.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/project_card.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/project_tree.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/session_drawer.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/settings_modal.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/sidebar.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/summary_bar.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/task_board.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/toasts.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/components/topbar.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui/state.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/hub_ui.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/mcp_proxy.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/progress.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/server.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/__init__.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/_grants.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/_helpers.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/agent.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/artifacts.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/bitbucket.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/ci.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/credentials.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/delegate.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/edit.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/edit_verify.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/edits.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/federate.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/filter.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/impact.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/jira.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/locks.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/memory.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/override.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/project.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/read.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/search.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/session.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/shell.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/status.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/tasks.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/tools/validate.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/api.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/app.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/access.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/bitbucket.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/chat.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/credentials.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/dashboard.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/edits.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/enforcement.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/instructions.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/jira.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/memory.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/sessions.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/settings.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/sidebar.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/components/tasks.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/icons.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/pm_shared.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/shared.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui/theme.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui_legacy.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/cli/ui_nano.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/code_context_control.egg-info/dependency_links.txt +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/code_context_control.egg-info/entry_points.txt +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/code_context_control.egg-info/requires.txt +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/code_context_control.egg-info/top_level.txt +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/core/__init__.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/core/config.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/core/ide.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/core/mcp_toml.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/core/web_security.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/__init__.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/config.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/mcp_oracle.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/oracle_server.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/oracle_ui.html +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/__init__.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/activity_reporter.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/api_auth.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/c3_bridge.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/chat_engine.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/chat_poll.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/chat_store.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/cross_memory.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/discovery_audit.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/federated_graph.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/health_checker.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/insight_engine.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/local_session.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/memory_reader.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/memory_writer.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/mobile_api.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/project_scanner.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/review_agent.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/tool_executor.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/services/tool_registry.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/activity.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/agents.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/app.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/busy.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/chat/conversations.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/chat/input.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/chat/markdown.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/chat/send.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/chat/stream_renderer.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/chat/toolbar.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/core.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/crossgraph.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/header.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/insights.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/projects.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/qrcode.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/settings.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/suggestions.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/oracle/ui/theme_tabs.js +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/__init__.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/access_guard.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/access_telemetry.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/activity_log.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/agent_base.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/agent_locks.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/agents.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/artifact_defs.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/artifact_store.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/auto_memory.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/background_service.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/bench/__init__.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/bench/external/__init__.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/bench/external/aider_polyglot.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/bench/external/swe_bench.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/benchmark_dashboard.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/bitbucket_client.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/bitbucket_credentials.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/ci_act.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/ci_cache.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/ci_expr.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/ci_failures.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/ci_github.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/ci_impact.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/ci_intel.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/ci_runner.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/ci_workflow.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/circuit_breaker.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/claude_md.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/compressor.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/context_snapshot.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/conversation_store.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/cred_telemetry.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/credential_store.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/doc_index.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/e2e_benchmark.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/e2e_evaluator.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/e2e_tasks.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/edit_ledger.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/embedding_index.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/enforcement_policy.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/error_reporting.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/file_memory.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/git_context.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/hub_service.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/indexer.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/jira_client.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/jira_cloud.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/jira_credentials.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/jira_data_center.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/jira_links.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/mask_activation.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/mask_mirror.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/mask_presets.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/memory.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/memory_consolidator.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/memory_distiller.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/memory_graph.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/memory_grounder.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/memory_queue.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/memory_scorer.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/metrics.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/notifications.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/ollama_bridge.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/ollama_client.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/ollama_credentials.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/oracle_service.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/output_filter.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/override_grants.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/override_policy.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/override_requests.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/override_wake.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/parser.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/project_manager.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/project_runtime.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/protocol.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/proxy_state.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/repo_map.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/repo_shape.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/retention.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/retrieval_broker.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/router.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/runtime.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/scanner.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/session_benchmark.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/session_manager.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/session_preloader.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/subprojects.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/task_store.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/telemetry.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/text_index.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/time_tracker.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/tool_classifier.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/transcript_index.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/validation_cache.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/vector_store.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/watcher.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/services/win_subprocess.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/setup.cfg +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_access_builtin_optout.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_access_guard.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_access_guard_meta.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_access_guard_shell_project.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_access_guard_surfaces.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_access_guard_wiring.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_activity_reporter.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_agent_locks.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_aider_polyglot.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_antigravity_transition.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_artifact_store.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_artifact_tool.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_background_service.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_bitbucket_cli_smoke.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_bitbucket_client.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_bitbucket_config_fallback.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_bitbucket_credentials.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_bitbucket_tool.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_c3_bridge_project_artifacts.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_c3_shell.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_chat_poll.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_ci_act.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_ci_cache.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_ci_expr.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_ci_impact.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_ci_intel.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_ci_runner.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_ci_workflow.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_circuit_breaker.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_claude_md_merge.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_cli_smoke.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_compressor_large_file.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_cred_telemetry.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_credential_login_kind.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_credential_store.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_credential_ui_parity.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_credentials_routes.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_credentials_tool.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_creds_cli_smoke.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_delegate_cascade.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_delegate_version_probe.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_e2e_benchmark.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_edit_ledger_deadline.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_edit_ledger_hook.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_edit_locking.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_edit_normalization.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_edit_verify.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_embedding_index_deadlock.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_enforcement_flip.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_enforcement_policy.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_enforcement_routes.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_federated_graph.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_filter_backoff.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_ghost_files.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_ghost_generation.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_git_branch_awareness.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hook_access_guard.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hook_prompt_recall.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hook_smoke.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hook_state.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hook_utf8_stdio.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hub_credentials_route.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hub_credentials_routes_write.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hub_enforcement_routes.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hub_inspect_api.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hub_locks_routes.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hub_oracle_service_routes.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hub_server_smoke.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_hub_subproject_links.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_ide_workflow_adaptation.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_init_clear_preserves_user_content.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_init_scan.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_install_mcp_entrypoint.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_jira_cli_smoke.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_jira_client.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_jira_config_fallback.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_jira_credentials.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_jira_links.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_jira_routes.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_jira_tool.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_lazy_store_init.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_mask_guard.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_mask_routes.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_mask_surfaces.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_mcp_host_guard.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_mcp_memory_timeout.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_mcp_server_smoke.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_mcp_toml.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_memory_distiller.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_memory_graph_api.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_memory_queue.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_memory_system.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_mobile_api.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_mobile_extras.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_mobile_override_routes.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_mobile_security_api.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_no_stale_model_pins.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_notification_dedup.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_notification_discipline.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_ollama_credentials.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_api_auth.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_apikey_api.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_c3_bridge.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_chat_engine.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_chat_store.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_discovery_api.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_discovery_audit.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_health_probe.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_local_auth.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_ollama_bridge.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_scanner_cache.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_security_fixes.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_session_bootstrap.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_subproject_awareness.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_oracle_ui_bundle.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_output_filter.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_override_grants.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_override_grants_mcp.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_override_requests.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_override_wake.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_permissions.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_pm_api.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_project_manager.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_project_manager_merge.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_project_tool.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_read_coercion.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_read_edit_parity.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_related_facts.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_repo_map.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_repo_shape.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_response_boilerplate.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_retention.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_review_digest.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_service_durability.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_session_benchmark.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_session_budget.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_shell_creds.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_shell_robustness.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_subproject_exclusion.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_subproject_federation.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_subprojects.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_swe_bench.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_task_store.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_task_tool.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_time_tracker.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_token_telemetry.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_tool_registry.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_upgrade_and_version.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_validate.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_vault_write_guard.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_version_sync.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_web_security.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tests/test_windows_reliability.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/__init__.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/backend.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/main.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/__init__.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/benchmark_view.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/claudemd_view.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/compress_view.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/index_view.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/init_view.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/mcp_view.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/optimize_view.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/pipe_view.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/projects_view.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/search_view.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/session_view.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/stats.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/screens/ui_view.py +0 -0
- {code_context_control-2.92.0 → code_context_control-2.92.1}/tui/theme.tcss +0 -0
{code_context_control-2.92.0/code_context_control.egg-info → code_context_control-2.92.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-context-control
|
|
3
|
-
Version: 2.92.
|
|
3
|
+
Version: 2.92.1
|
|
4
4
|
Summary: Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub.
|
|
5
5
|
Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -7,6 +7,7 @@ All hook reads/writes of enforcement state MUST go through this module.
|
|
|
7
7
|
"""
|
|
8
8
|
import json
|
|
9
9
|
import os
|
|
10
|
+
import re
|
|
10
11
|
import sys
|
|
11
12
|
import traceback
|
|
12
13
|
from datetime import datetime, timezone
|
|
@@ -440,6 +441,43 @@ def get_tool_output(data: dict) -> tuple:
|
|
|
440
441
|
return resp if isinstance(resp, str) else "", False
|
|
441
442
|
|
|
442
443
|
|
|
444
|
+
# A c3 tool that failed did not read, search or edit anything, so its
|
|
445
|
+
# completion is not evidence that c3 was used (field report 2026-08-22,
|
|
446
|
+
# ISSUE-3: c3_compress on a missing path -> "Error: File not found" -> the
|
|
447
|
+
# sticky unlock and the signal both fired -> native Write allowed). Tools
|
|
448
|
+
# report failure as a leading "Error" / "[<tool>:error]" line, the masked-path
|
|
449
|
+
# refusal tag, or an MCP isError flag.
|
|
450
|
+
_FAILED_RESPONSE_RE = re.compile(
|
|
451
|
+
r"^\s*(?:Error\b|\[[a-z0-9_\-]+:error\]|\[c3-mask:unsupported\])", re.IGNORECASE)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def response_text_failed(text) -> bool:
|
|
455
|
+
"""True when a c3 tool's response text reads as a failure."""
|
|
456
|
+
return bool(_FAILED_RESPONSE_RE.match(str(text or "")))
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def tool_response_failed(data: dict) -> bool:
|
|
460
|
+
"""True when the PostToolUse payload carries a failed c3 tool response.
|
|
461
|
+
|
|
462
|
+
Handles Claude Code's plain string, an MCP-style dict with ``isError`` /
|
|
463
|
+
``content`` parts, a bare list of parts, and Gemini's ``llmContent``.
|
|
464
|
+
"""
|
|
465
|
+
resp = data.get("tool_response")
|
|
466
|
+
if isinstance(resp, dict):
|
|
467
|
+
if resp.get("isError") is True or resp.get("is_error") is True:
|
|
468
|
+
return True
|
|
469
|
+
content = resp.get("content")
|
|
470
|
+
if isinstance(content, list):
|
|
471
|
+
text = "\n".join((p.get("text") or "") for p in content if isinstance(p, dict))
|
|
472
|
+
return response_text_failed(text)
|
|
473
|
+
text, _ = get_tool_output(data)
|
|
474
|
+
return response_text_failed(text)
|
|
475
|
+
if isinstance(resp, list):
|
|
476
|
+
text = "\n".join((p.get("text") or "") if isinstance(p, dict) else str(p) for p in resp)
|
|
477
|
+
return response_text_failed(text)
|
|
478
|
+
return response_text_failed(resp if isinstance(resp, str) else "")
|
|
479
|
+
|
|
480
|
+
|
|
443
481
|
def get_tool_input_path(data: dict) -> str:
|
|
444
482
|
"""Extract file path from tool_input, handling Claude (file_path),
|
|
445
483
|
Gemini (path), and NotebookEdit (notebook_path)."""
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Best-effort extraction of the files a shell command writes.
|
|
2
|
+
|
|
3
|
+
Field report 2026-08-22, ISSUE-1's buried finding: ``Bash`` sits in the
|
|
4
|
+
PreToolUse matcher, but tool discipline never looked at what a shell
|
|
5
|
+
command does to files. An inline ``python -c`` write, ``cat > f <<EOF`` and
|
|
6
|
+
``sed -i`` were never nudged toward c3_edit and never reached the ledger,
|
|
7
|
+
so every agent that met a blocked ``Write`` simply went round it. This
|
|
8
|
+
module is the one place that answers "which files does this command
|
|
9
|
+
probably write?" for both the PreToolUse nudge (hook_pretool_enforce) and
|
|
10
|
+
the after-the-fact ledger entry (hook_edit_ledger).
|
|
11
|
+
|
|
12
|
+
Deliberately conservative, like the access guard's shell scan: a missed
|
|
13
|
+
target costs one missing advisory line and one missing ledger row; a false
|
|
14
|
+
target costs a misleading hint, so tokens that are shell syntax, streams
|
|
15
|
+
(``/dev/null``, ``&1``) or pip-style ``>=`` version specs are skipped.
|
|
16
|
+
Paths resolve against the directory the command runs in, following ``cd``.
|
|
17
|
+
Never raises.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
_SEGMENT_SPLIT = re.compile(r"&&|\|\||[;\n]")
|
|
26
|
+
_CD_RE = re.compile(r"""^\s*cd\s+(?:/d\s+)?(?P<dir>"[^"]+"|'[^']+'|\S+)\s*$""", re.IGNORECASE)
|
|
27
|
+
# `>` / `>>` / `>|` preceded by whitespace or line start (so `foo>=3.0` is not a redirect).
|
|
28
|
+
_REDIRECT_RE = re.compile(r"""(?:^|\s)\d?>{1,2}\|?\s*(?P<t>"[^"]+"|'[^']+'|\S+)""")
|
|
29
|
+
_TEE_RE = re.compile(r"""(?:^|\|)\s*tee\s+(?:-[a-zA-Z]+\s+)*(?P<t>"[^"]+"|'[^']+'|\S+)""")
|
|
30
|
+
_SED_INPLACE_RE = re.compile(r"""(?:^|\s)sed\s+(?P<rest>.*)$""")
|
|
31
|
+
_PY_OPEN_RE = re.compile(r"""open\(\s*(?P<q>['"])(?P<t>[^'"]+)(?P=q)\s*,\s*['"][wax]""")
|
|
32
|
+
_PY_WRITE_TEXT_RE = re.compile(r"""Path\(\s*(?P<q>['"])(?P<t>[^'"]+)(?P=q)\s*\)\s*\.write_(?:text|bytes)\(""")
|
|
33
|
+
_FILE_CMDS = {"cp", "mv", "touch", "rm", "install"}
|
|
34
|
+
_STREAMS = {"/dev/null", "/dev/stdout", "/dev/stderr", "nul", "&1", "&2", "-"}
|
|
35
|
+
_SYNTAX = set("$`(){}|<>*?")
|
|
36
|
+
MAX_TARGETS = 20
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _clean(tok: str) -> str:
|
|
40
|
+
return tok.strip().strip("\"'`;,")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _skip(tok: str) -> bool:
|
|
44
|
+
if not tok or tok.startswith("-") or tok.startswith("=") or tok in _STREAMS:
|
|
45
|
+
return True
|
|
46
|
+
if tok.lower() in _STREAMS or tok.startswith("&"):
|
|
47
|
+
return True
|
|
48
|
+
return bool(_SYNTAX & set(tok))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _segment_targets(segment: str) -> list[str]:
|
|
52
|
+
out: list[str] = []
|
|
53
|
+
for m in _REDIRECT_RE.finditer(segment):
|
|
54
|
+
out.append(_clean(m.group("t")))
|
|
55
|
+
for m in _TEE_RE.finditer(segment):
|
|
56
|
+
out.append(_clean(m.group("t")))
|
|
57
|
+
for m in _PY_OPEN_RE.finditer(segment):
|
|
58
|
+
out.append(m.group("t"))
|
|
59
|
+
for m in _PY_WRITE_TEXT_RE.finditer(segment):
|
|
60
|
+
out.append(m.group("t"))
|
|
61
|
+
words = [w for w in re.split(r"\s+", segment.strip()) if w]
|
|
62
|
+
if words:
|
|
63
|
+
head = words[0].rsplit("/", 1)[-1].lower()
|
|
64
|
+
args = [_clean(w) for w in words[1:] if not w.startswith("-")]
|
|
65
|
+
if head in ("cp", "mv", "install") and len(args) >= 2:
|
|
66
|
+
out.append(args[-1])
|
|
67
|
+
elif head in ("touch", "rm") and args:
|
|
68
|
+
out.extend(args)
|
|
69
|
+
elif head == "sed":
|
|
70
|
+
flags = [w for w in words[1:] if w.startswith("-")]
|
|
71
|
+
if any(f.startswith("-i") or f == "--in-place" for f in flags):
|
|
72
|
+
# last non-flag, non-script argument(s): the files
|
|
73
|
+
files = [a for a in args if not (a.startswith("s/") or a.startswith("s|") or "/" in a and a.count("/") >= 2 and a.startswith("s"))]
|
|
74
|
+
out.extend(files[-2:] if files else [])
|
|
75
|
+
return [t for t in out if not _skip(t)]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _cd_target(segment: str, cwd: str) -> str | None:
|
|
79
|
+
m = _CD_RE.match(segment)
|
|
80
|
+
if not m:
|
|
81
|
+
return None
|
|
82
|
+
target = m.group("dir").strip("\"'")
|
|
83
|
+
try:
|
|
84
|
+
return target if os.path.isabs(target) else os.path.normpath(os.path.join(cwd, target))
|
|
85
|
+
except (OSError, ValueError):
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def shell_write_targets(cmd: str, cwd: str) -> list[str]:
|
|
90
|
+
"""Absolute paths the command probably writes, in order, de-duplicated.
|
|
91
|
+
|
|
92
|
+
``cwd`` is the directory the command runs in (the project root for
|
|
93
|
+
Claude Code hooks). Relative targets resolve against it, and a ``cd``
|
|
94
|
+
segment moves it for the segments that follow.
|
|
95
|
+
"""
|
|
96
|
+
found: list[str] = []
|
|
97
|
+
seen: set[str] = set()
|
|
98
|
+
here = cwd
|
|
99
|
+
try:
|
|
100
|
+
for segment in _SEGMENT_SPLIT.split(cmd or ""):
|
|
101
|
+
for tok in _segment_targets(segment):
|
|
102
|
+
if re.match(r"^/[a-z]/", tok): # MSYS /c/foo -> C:/foo
|
|
103
|
+
tok = f"{tok[1]}:{tok[2:]}"
|
|
104
|
+
try:
|
|
105
|
+
p = Path(tok) if os.path.isabs(tok) else Path(here) / tok
|
|
106
|
+
key = os.path.normcase(os.path.normpath(str(p)))
|
|
107
|
+
except (OSError, ValueError):
|
|
108
|
+
continue
|
|
109
|
+
if key in seen:
|
|
110
|
+
continue
|
|
111
|
+
seen.add(key)
|
|
112
|
+
found.append(str(p))
|
|
113
|
+
if len(found) >= MAX_TARGETS:
|
|
114
|
+
return found
|
|
115
|
+
moved = _cd_target(segment, here)
|
|
116
|
+
if moved is not None:
|
|
117
|
+
here = moved
|
|
118
|
+
except Exception:
|
|
119
|
+
return found
|
|
120
|
+
return found
|
|
@@ -26,7 +26,7 @@ from pathlib import Path
|
|
|
26
26
|
|
|
27
27
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
28
28
|
|
|
29
|
-
from cli._hook_utils import log_hook_error, record_c3_signal # noqa: E402
|
|
29
|
+
from cli._hook_utils import log_hook_error, record_c3_signal, tool_response_failed # noqa: E402
|
|
30
30
|
|
|
31
31
|
# Tools that unlock generic read operations (Grep/Glob without a file path)
|
|
32
32
|
_READ_UNLOCK_TOOLS = {"c3_search", "c3_compress", "c3_filter", "c3_read", "c3_impact", "c3_validate"}
|
|
@@ -45,6 +45,10 @@ def run(payload: dict, project_path: Path | None = None):
|
|
|
45
45
|
if not short_name.startswith("c3_"):
|
|
46
46
|
return None
|
|
47
47
|
|
|
48
|
+
# A failed call read nothing and edited nothing: no signal (ISSUE-3).
|
|
49
|
+
if tool_response_failed(payload):
|
|
50
|
+
return None
|
|
51
|
+
|
|
48
52
|
record_c3_signal(
|
|
49
53
|
short_name,
|
|
50
54
|
short_name in _READ_UNLOCK_TOOLS,
|
|
@@ -152,6 +152,10 @@ def _routes(event: str, raw_tool: str, norm_tool: str, host: str = HOST_CLAUDE):
|
|
|
152
152
|
yield "hook_artifact"
|
|
153
153
|
if raw_tool in _GHOST_TOOLS:
|
|
154
154
|
yield "hook_ghost_files"
|
|
155
|
+
# Shell file writes land in the ledger after the fact (after the
|
|
156
|
+
# ghost sweep, so a 0-byte redirect artifact is never logged).
|
|
157
|
+
if norm_tool == "Bash":
|
|
158
|
+
yield "hook_edit_ledger"
|
|
155
159
|
elif event == "stop":
|
|
156
160
|
yield "hook_session_stats"
|
|
157
161
|
yield "hook_auto_snapshot"
|
|
@@ -139,6 +139,9 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
|
|
|
139
139
|
"""
|
|
140
140
|
tool_name = normalize_tool_name(payload.get("tool_name", ""))
|
|
141
141
|
|
|
142
|
+
if tool_name == "Bash":
|
|
143
|
+
return _run_shell(payload, project_path)
|
|
144
|
+
|
|
142
145
|
if tool_name not in ("Edit", "Write", "NotebookEdit"):
|
|
143
146
|
return None
|
|
144
147
|
|
|
@@ -230,6 +233,89 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
|
|
|
230
233
|
}
|
|
231
234
|
|
|
232
235
|
|
|
236
|
+
# A file named by the command counts as written by it only if it changed
|
|
237
|
+
# this recently — `rm`, a stale name in a quoted string, or a path that was
|
|
238
|
+
# merely mentioned must not become a ledger row.
|
|
239
|
+
_SHELL_WRITE_WINDOW_S = 120
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _run_shell(payload: dict, project_path: Path | None) -> dict | None:
|
|
243
|
+
"""After-the-fact ledger rows for files a shell command wrote.
|
|
244
|
+
|
|
245
|
+
Field report 2026-08-22 (ISSUE-1): writes through `python -c`, heredocs
|
|
246
|
+
and `sed -i` never reached the ledger, so the audit trail had holes
|
|
247
|
+
exactly where the agent had gone round the Write block. There is no
|
|
248
|
+
pre-edit snapshot to take here — the command already ran — so the row
|
|
249
|
+
is marked change_type "shell" and carries the command.
|
|
250
|
+
"""
|
|
251
|
+
tool_input = payload.get("tool_input", {}) or {}
|
|
252
|
+
cmd = str(tool_input.get("command") or "")
|
|
253
|
+
if not cmd.strip():
|
|
254
|
+
return None
|
|
255
|
+
if project_path is None:
|
|
256
|
+
project_path = Path.cwd()
|
|
257
|
+
c3_dir = project_path / ".c3"
|
|
258
|
+
if not c3_dir.exists():
|
|
259
|
+
return None
|
|
260
|
+
config = load_hybrid_config(str(project_path))
|
|
261
|
+
ledger_cfg = config.get("edit_ledger", {})
|
|
262
|
+
if not ledger_cfg.get("enabled", True):
|
|
263
|
+
return None
|
|
264
|
+
tracking_level = ledger_cfg.get("tracking_level", "standard")
|
|
265
|
+
try:
|
|
266
|
+
from cli._shell_writes import shell_write_targets
|
|
267
|
+
except Exception:
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
ledger_file = c3_dir / "edit_ledger.jsonl"
|
|
271
|
+
now = datetime.now(timezone.utc)
|
|
272
|
+
root = project_path.resolve()
|
|
273
|
+
logged = []
|
|
274
|
+
for target in shell_write_targets(cmd, str(project_path)):
|
|
275
|
+
p = Path(target)
|
|
276
|
+
try:
|
|
277
|
+
if not p.is_file() or p.suffix.lower() not in EDITABLE_EXTS:
|
|
278
|
+
continue
|
|
279
|
+
rel = str(p.resolve().relative_to(root)).replace("\\", "/")
|
|
280
|
+
if now.timestamp() - p.stat().st_mtime > _SHELL_WRITE_WINDOW_S:
|
|
281
|
+
continue
|
|
282
|
+
except (OSError, ValueError):
|
|
283
|
+
continue
|
|
284
|
+
entry = {
|
|
285
|
+
"id": f"edit_{now.strftime('%Y%m%d_%H%M%S')}_{_next_seq(ledger_file, now):03d}_{uuid.uuid4().hex[:4]}",
|
|
286
|
+
"timestamp": now.isoformat(),
|
|
287
|
+
"session_id": str(payload.get("session_id") or ""),
|
|
288
|
+
"file": rel,
|
|
289
|
+
"change_type": "shell",
|
|
290
|
+
"summary": "shell" if tracking_level == "minimal" else f"shell: {cmd.strip()[:160]}",
|
|
291
|
+
"lines_changed": None,
|
|
292
|
+
"version": _next_version(ledger_file, rel),
|
|
293
|
+
"git": {},
|
|
294
|
+
"diff_summary": "",
|
|
295
|
+
"git_pending": tracking_level != "minimal",
|
|
296
|
+
"tags": ["auto", "shell"] if ledger_cfg.get("auto_tag", True) else ["shell"],
|
|
297
|
+
}
|
|
298
|
+
if tracking_level == "detailed":
|
|
299
|
+
entry["detail"] = {"command": cmd.strip()[:300]}
|
|
300
|
+
with open(ledger_file, "a", encoding="utf-8") as f:
|
|
301
|
+
f.write(json.dumps(entry) + "\n")
|
|
302
|
+
logged.append(f"{rel} {entry['version']}")
|
|
303
|
+
try:
|
|
304
|
+
from services.repo_map import is_structural_change, mark_map_dirty
|
|
305
|
+
if is_structural_change(rel, "shell"):
|
|
306
|
+
mark_map_dirty(project_path, f"shell:{rel}")
|
|
307
|
+
except Exception:
|
|
308
|
+
pass
|
|
309
|
+
if not logged:
|
|
310
|
+
return None
|
|
311
|
+
return {
|
|
312
|
+
"_text": (
|
|
313
|
+
f"[c3:ledger] shell write logged after the fact: {', '.join(logged)} "
|
|
314
|
+
f"— no pre-edit snapshot; prefer c3_edit for file changes."
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
|
|
233
319
|
def main():
|
|
234
320
|
ensure_utf8_stdio()
|
|
235
321
|
try:
|
|
@@ -21,6 +21,7 @@ from cli._hook_utils import ( # noqa: E402
|
|
|
21
21
|
emit_additional_context,
|
|
22
22
|
log_hook_error,
|
|
23
23
|
record_json_unlocks,
|
|
24
|
+
tool_response_failed,
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
EDITABLE_EXTS = {
|
|
@@ -98,6 +99,13 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
|
|
|
98
99
|
|
|
99
100
|
base = project_path if project_path is not None else Path.cwd()
|
|
100
101
|
|
|
102
|
+
# "c3_compress on a path that does not exist" returned an error and still
|
|
103
|
+
# unlocked native Write for that path (field report 2026-08-22, ISSUE-3).
|
|
104
|
+
# An error response gathered no context, so it grants nothing. New files
|
|
105
|
+
# are created through c3_edit(old_string=""), which needs no unlock.
|
|
106
|
+
if tool_response_failed(payload):
|
|
107
|
+
return None
|
|
108
|
+
|
|
101
109
|
tool_input = payload.get("tool_input", {}) or {}
|
|
102
110
|
tool_response = payload.get("tool_response", "")
|
|
103
111
|
if isinstance(tool_response, dict):
|
|
@@ -35,6 +35,7 @@ advisory path instead of granting stale unlocks.
|
|
|
35
35
|
Supports both Claude Code and Gemini CLI via _hook_utils.
|
|
36
36
|
"""
|
|
37
37
|
import json
|
|
38
|
+
import os
|
|
38
39
|
import re
|
|
39
40
|
import sys
|
|
40
41
|
from datetime import datetime, timezone
|
|
@@ -54,8 +55,17 @@ from _hook_utils import ( # noqa: E402
|
|
|
54
55
|
log_hook_error,
|
|
55
56
|
normalize_tool_name,
|
|
56
57
|
record_unlocked_files,
|
|
58
|
+
response_text_failed,
|
|
57
59
|
)
|
|
58
60
|
|
|
61
|
+
try:
|
|
62
|
+
from _shell_writes import shell_write_targets
|
|
63
|
+
except Exception: # pragma: no cover - package-style import (tests, dispatcher)
|
|
64
|
+
try:
|
|
65
|
+
from cli._shell_writes import shell_write_targets
|
|
66
|
+
except Exception:
|
|
67
|
+
shell_write_targets = None
|
|
68
|
+
|
|
59
69
|
try:
|
|
60
70
|
from services import access_telemetry, enforcement_policy
|
|
61
71
|
except Exception: # pragma: no cover — degrade to hardcoded strict behavior
|
|
@@ -142,7 +152,8 @@ _REDIRECTS = {
|
|
|
142
152
|
),
|
|
143
153
|
"Write": (
|
|
144
154
|
"Use c3_edit(file_path='...', old_string='...', new_string='...', summary='...') "
|
|
145
|
-
"for file modifications. For
|
|
155
|
+
"for file modifications. For a NEW file, c3_edit(file_path='...', old_string='', "
|
|
156
|
+
"new_string='<content>') creates it and logs it."
|
|
146
157
|
),
|
|
147
158
|
}
|
|
148
159
|
|
|
@@ -313,6 +324,11 @@ def _check_c3_used(
|
|
|
313
324
|
tool = entry.get("tool", "")
|
|
314
325
|
if tool not in allowed:
|
|
315
326
|
continue
|
|
327
|
+
# ISSUE-3: the log used to count a call that returned "Error: File
|
|
328
|
+
# not found" as "c3 was used". Newer entries carry ok=False; older
|
|
329
|
+
# ones are judged by the summary text.
|
|
330
|
+
if entry.get("ok") is False or response_text_failed(entry.get("result_summary") or ""):
|
|
331
|
+
continue
|
|
316
332
|
|
|
317
333
|
if native_target:
|
|
318
334
|
grant = _C3_GRANTS.get(tool, required_cat)
|
|
@@ -514,6 +530,9 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
|
|
|
514
530
|
"""
|
|
515
531
|
tool_name = normalize_tool_name(payload.get("tool_name", ""))
|
|
516
532
|
|
|
533
|
+
if tool_name == "Bash":
|
|
534
|
+
return _shell_advisory(payload, project_path)
|
|
535
|
+
|
|
517
536
|
if tool_name not in _PREREQS:
|
|
518
537
|
return None # Not a tool we enforce — pass through
|
|
519
538
|
|
|
@@ -617,6 +636,55 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
|
|
|
617
636
|
}, policy)
|
|
618
637
|
|
|
619
638
|
|
|
639
|
+
def _shell_advisory(payload: dict, project_path: Path | None) -> dict | None:
|
|
640
|
+
"""Bash is the escape hatch tool discipline never looked at (field
|
|
641
|
+
report 2026-08-22, ISSUE-1's buried finding): an inline ``python -c`` write
|
|
642
|
+
or a heredoc was never nudged toward c3_edit and never snapshotted. It
|
|
643
|
+
is NOT denied — blocking shell writes outright would break far more
|
|
644
|
+
legitimate work than it protects — it gets the advisory hint that names
|
|
645
|
+
the files, in strict and advisory modes alike, and hook_edit_ledger
|
|
646
|
+
records the writes after the fact. A fresh write-class c3 signal
|
|
647
|
+
(c3_edit just ran) means the agent is already on the c3 path: silent.
|
|
648
|
+
"""
|
|
649
|
+
if shell_write_targets is None:
|
|
650
|
+
return None
|
|
651
|
+
tool_input = payload.get("tool_input", {}) or {}
|
|
652
|
+
cmd = str(tool_input.get("command") or "")
|
|
653
|
+
if not cmd.strip():
|
|
654
|
+
return None
|
|
655
|
+
base = project_path if project_path is not None else Path.cwd()
|
|
656
|
+
policy = _resolve_policy(base)
|
|
657
|
+
mode = getattr(policy, "mode", None) or "strict"
|
|
658
|
+
if mode == "off":
|
|
659
|
+
return _policy_warnings(policy)
|
|
660
|
+
targets = [t for t in shell_write_targets(cmd, str(base))
|
|
661
|
+
if not _outside_project(base, {"file_path": t})]
|
|
662
|
+
if not targets:
|
|
663
|
+
return _policy_warnings(policy)
|
|
664
|
+
session_id = str(payload.get("session_id") or "")
|
|
665
|
+
state = load_enforcement_state(base, session_id=session_id)
|
|
666
|
+
ttl_s = getattr(policy, "signal_ttl_s", _SIGNAL_MAX_AGE_SECS)
|
|
667
|
+
recent, _read_unlocked, signal_tool = _check_signal(state, ttl_s)
|
|
668
|
+
if recent and signal_tool in _PREREQS["Write"]:
|
|
669
|
+
return _policy_warnings(policy)
|
|
670
|
+
shown = []
|
|
671
|
+
for t in targets[:3]:
|
|
672
|
+
try: # display only; no path resolution here (canonical_key owns that)
|
|
673
|
+
shown.append(os.path.relpath(t, str(base)).replace("\\", "/"))
|
|
674
|
+
except (OSError, ValueError):
|
|
675
|
+
shown.append(t)
|
|
676
|
+
more = f" (+{len(targets) - 3} more)" if len(targets) > 3 else ""
|
|
677
|
+
return _with_warnings({
|
|
678
|
+
"additionalContext": (
|
|
679
|
+
f"[c3:hint] This shell command looks like it writes {', '.join(shown)}{more}. "
|
|
680
|
+
f"Shell writes bypass c3_edit: no pre-edit snapshot, and the ledger can only "
|
|
681
|
+
f"record them after the fact as `shell` changes. Prefer "
|
|
682
|
+
f"c3_edit(file_path='...', old_string='...', new_string='...') — old_string='' "
|
|
683
|
+
f"creates a file. (advisory: shell is never blocked)"
|
|
684
|
+
)
|
|
685
|
+
}, policy)
|
|
686
|
+
|
|
687
|
+
|
|
620
688
|
def _record_block(tool_name: str, tool_input: dict, session_id: str,
|
|
621
689
|
base: Path) -> None:
|
|
622
690
|
"""Log a discipline block so `c3 access stats` can show what it costs."""
|
|
@@ -415,7 +415,15 @@ def _finalize_response(ctx: Context, tool_name: str, args: dict,
|
|
|
415
415
|
svc.activity_log.log("budget_auto_reset", {"gap_seconds": gap_reset_seconds})
|
|
416
416
|
|
|
417
417
|
svc.session_mgr.log_tool_call(tool_name, args, summary)
|
|
418
|
-
|
|
418
|
+
# ok=False lets hook_pretool_enforce's activity scan skip a failed call
|
|
419
|
+
# (ISSUE-3: "Error: File not found" used to count as "c3 was used").
|
|
420
|
+
try:
|
|
421
|
+
from cli._hook_utils import response_text_failed as _failed
|
|
422
|
+
call_ok = not _failed(response)
|
|
423
|
+
except Exception:
|
|
424
|
+
call_ok = True
|
|
425
|
+
svc.activity_log.log("tool_call", {"tool": tool_name, "args": args,
|
|
426
|
+
"result_summary": summary, "ok": call_ok})
|
|
419
427
|
tracker = getattr(svc, "time_tracker", None)
|
|
420
428
|
if tracker is not None:
|
|
421
429
|
try:
|
|
@@ -147,7 +147,8 @@ def _compress_single(file_path: str, mode: str, svc, finalize, maybe_facts) -> s
|
|
|
147
147
|
|
|
148
148
|
if mode in ("map", "dense_map"):
|
|
149
149
|
if not full.exists():
|
|
150
|
-
return "[file_map:error] not found"
|
|
150
|
+
return ("[file_map:error] not found. To create a new file use "
|
|
151
|
+
"c3_edit(file_path=..., old_string='', new_string=<content>).")
|
|
151
152
|
rel = str(full.resolve().relative_to(Path(svc.project_path).resolve())).replace("\\", "/")
|
|
152
153
|
queued = svc.file_memory.drain_queue()
|
|
153
154
|
completed = []
|
{code_context_control-2.92.0 → code_context_control-2.92.1/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.92.
|
|
3
|
+
Version: 2.92.1
|
|
4
4
|
Summary: Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub.
|
|
5
5
|
Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -3,6 +3,7 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
cli/__init__.py
|
|
5
5
|
cli/_hook_utils.py
|
|
6
|
+
cli/_shell_writes.py
|
|
6
7
|
cli/c3.py
|
|
7
8
|
cli/docs.html
|
|
8
9
|
cli/edits.html
|
|
@@ -321,6 +322,7 @@ tests/test_embedding_index_deadlock.py
|
|
|
321
322
|
tests/test_enforcement_flip.py
|
|
322
323
|
tests/test_enforcement_policy.py
|
|
323
324
|
tests/test_enforcement_routes.py
|
|
325
|
+
tests/test_failed_calls_do_not_unlock.py
|
|
324
326
|
tests/test_federated_graph.py
|
|
325
327
|
tests/test_filter_backoff.py
|
|
326
328
|
tests/test_ghost_files.py
|
|
@@ -411,6 +413,8 @@ tests/test_session_benchmark.py
|
|
|
411
413
|
tests/test_session_budget.py
|
|
412
414
|
tests/test_shell_creds.py
|
|
413
415
|
tests/test_shell_robustness.py
|
|
416
|
+
tests/test_shell_write_ledger.py
|
|
417
|
+
tests/test_shell_writes.py
|
|
414
418
|
tests/test_subproject_exclusion.py
|
|
415
419
|
tests/test_subproject_federation.py
|
|
416
420
|
tests/test_subprojects.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code-context-control"
|
|
7
|
-
version = "2.92.
|
|
7
|
+
version = "2.92.1"
|
|
8
8
|
description = "Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""A c3_* call that FAILED is not evidence that c3 was used (ISSUE-3).
|
|
2
|
+
|
|
3
|
+
Before: c3_compress on a missing path returned "Error: File not found",
|
|
4
|
+
hook_c3_signal recorded a fresh signal, hook_edit_unlock stuck an unlock on
|
|
5
|
+
that path, and the activity log counted the call — so native Write on the
|
|
6
|
+
very path was allowed. Now all three routes read the result first.
|
|
7
|
+
"""
|
|
8
|
+
import json
|
|
9
|
+
import shutil
|
|
10
|
+
import sys
|
|
11
|
+
import tempfile
|
|
12
|
+
import unittest
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
16
|
+
sys.path.insert(0, str(REPO_ROOT))
|
|
17
|
+
|
|
18
|
+
from cli import _hook_utils # noqa: E402
|
|
19
|
+
from cli._hook_utils import ( # noqa: E402
|
|
20
|
+
load_enforcement_state,
|
|
21
|
+
response_text_failed,
|
|
22
|
+
tool_response_failed,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
sys.modules.setdefault("_hook_utils", _hook_utils)
|
|
26
|
+
|
|
27
|
+
from cli import hook_c3_signal, hook_edit_unlock # noqa: E402
|
|
28
|
+
from cli.hook_pretool_enforce import run as enforce # noqa: E402
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestFailureHeuristic(unittest.TestCase):
|
|
32
|
+
def test_text(self):
|
|
33
|
+
self.assertTrue(response_text_failed("Error: File not found: x.py"))
|
|
34
|
+
self.assertTrue(response_text_failed(" Error reading file"))
|
|
35
|
+
self.assertTrue(response_text_failed("[read:error] File not found: x"))
|
|
36
|
+
self.assertTrue(response_text_failed("[file_map:error] not found"))
|
|
37
|
+
self.assertTrue(response_text_failed("[c3-mask:unsupported] policy"))
|
|
38
|
+
self.assertFalse(response_text_failed("## results\nError handling lives in errors.py"))
|
|
39
|
+
self.assertFalse(response_text_failed("# a.py (.py) — 12 lines"))
|
|
40
|
+
self.assertFalse(response_text_failed(""))
|
|
41
|
+
self.assertFalse(response_text_failed(None))
|
|
42
|
+
|
|
43
|
+
def test_payload_shapes(self):
|
|
44
|
+
self.assertTrue(tool_response_failed({"tool_response": "Error: nope"}))
|
|
45
|
+
self.assertTrue(tool_response_failed({"tool_response": {"isError": True, "content": [{"type": "text", "text": "ok?"}]}}))
|
|
46
|
+
self.assertTrue(tool_response_failed({"tool_response": {"content": [{"type": "text", "text": "Error: x"}]}}))
|
|
47
|
+
self.assertTrue(tool_response_failed({"tool_response": [{"type": "text", "text": "[read:error] x"}]}))
|
|
48
|
+
self.assertTrue(tool_response_failed({"tool_response": {"llmContent": "Error: gemini shape"}}))
|
|
49
|
+
self.assertFalse(tool_response_failed({"tool_response": "fine"}))
|
|
50
|
+
self.assertFalse(tool_response_failed({"tool_response": {"content": [{"type": "text", "text": "fine"}]}}))
|
|
51
|
+
self.assertFalse(tool_response_failed({}))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _Project(unittest.TestCase):
|
|
55
|
+
def setUp(self):
|
|
56
|
+
self.tmp = Path(tempfile.mkdtemp())
|
|
57
|
+
(self.tmp / ".c3").mkdir()
|
|
58
|
+
_hook_utils.drain_state_warnings()
|
|
59
|
+
|
|
60
|
+
def tearDown(self):
|
|
61
|
+
_hook_utils.drain_state_warnings()
|
|
62
|
+
shutil.rmtree(self.tmp, ignore_errors=True)
|
|
63
|
+
|
|
64
|
+
def _payload(self, tool, file_path, response, session="s1"):
|
|
65
|
+
return {"tool_name": tool, "session_id": session,
|
|
66
|
+
"tool_input": {"file_path": file_path, "mode": "map"},
|
|
67
|
+
"tool_response": response}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TestSignalHook(_Project):
|
|
71
|
+
def test_failed_call_writes_no_signal(self):
|
|
72
|
+
hook_c3_signal.run(self._payload("mcp__c3__c3_compress", "ghost.py", "Error: File not found: ghost.py"), self.tmp)
|
|
73
|
+
self.assertIsNone(load_enforcement_state(self.tmp, session_id="s1").get("last_c3_call"))
|
|
74
|
+
|
|
75
|
+
def test_successful_call_writes_signal(self):
|
|
76
|
+
hook_c3_signal.run(self._payload("mcp__c3__c3_compress", "a.py", "# a.py map"), self.tmp)
|
|
77
|
+
self.assertEqual(load_enforcement_state(self.tmp, session_id="s1")["last_c3_call"]["tool"], "c3_compress")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestUnlockHook(_Project):
|
|
81
|
+
def test_failed_compress_unlocks_nothing(self):
|
|
82
|
+
target = str(self.tmp / "ghost.py")
|
|
83
|
+
out = hook_edit_unlock.run(self._payload("mcp__c3__c3_compress", target, "Error: File not found"), self.tmp)
|
|
84
|
+
self.assertIsNone(out)
|
|
85
|
+
self.assertEqual(load_enforcement_state(self.tmp, session_id="s1").get("unlocked_files"), {})
|
|
86
|
+
deny = enforce({"tool_name": "Write", "session_id": "s1",
|
|
87
|
+
"tool_input": {"file_path": target, "content": "x"}}, project_path=self.tmp)
|
|
88
|
+
self.assertEqual(deny["hookSpecificOutput"]["permissionDecision"], "deny")
|
|
89
|
+
|
|
90
|
+
def test_successful_compress_still_unlocks(self):
|
|
91
|
+
target = str(self.tmp / "real.py")
|
|
92
|
+
out = hook_edit_unlock.run(self._payload("mcp__c3__c3_compress", target, "# real.py (.py) — 3 lines"), self.tmp)
|
|
93
|
+
self.assertIsNotNone(out)
|
|
94
|
+
self.assertTrue(load_enforcement_state(self.tmp, session_id="s1").get("unlocked_files"))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TestActivityLogScan(_Project):
|
|
98
|
+
def _log(self, tool, summary, ok=None):
|
|
99
|
+
entry = {"timestamp": "2026-08-22T12:00:00+00:00", "type": "tool_call", "tool": tool,
|
|
100
|
+
"args": {}, "result_summary": summary}
|
|
101
|
+
if ok is not None:
|
|
102
|
+
entry["ok"] = ok
|
|
103
|
+
with open(self.tmp / ".c3" / "activity_log.jsonl", "a", encoding="utf-8") as f:
|
|
104
|
+
f.write(json.dumps(entry) + "\n")
|
|
105
|
+
|
|
106
|
+
def _write(self):
|
|
107
|
+
return enforce({"tool_name": "Write", "session_id": "s9",
|
|
108
|
+
"tool_input": {"file_path": str(self.tmp / "n.py"), "content": "x"}}, project_path=self.tmp)
|
|
109
|
+
|
|
110
|
+
def test_failed_entry_by_flag_is_skipped(self):
|
|
111
|
+
self._log("c3_edit", "120->40tok", ok=False)
|
|
112
|
+
self.assertEqual(self._write()["hookSpecificOutput"]["permissionDecision"], "deny")
|
|
113
|
+
|
|
114
|
+
def test_failed_entry_by_text_is_skipped(self):
|
|
115
|
+
self._log("c3_edit", "Error: File not found: n.py")
|
|
116
|
+
self.assertEqual(self._write()["hookSpecificOutput"]["permissionDecision"], "deny")
|
|
117
|
+
|
|
118
|
+
def test_successful_entry_still_counts(self):
|
|
119
|
+
self._log("c3_edit", "edited n.py", ok=True)
|
|
120
|
+
self.assertNotIn("hookSpecificOutput", self._write() or {})
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
unittest.main()
|