code-context-control 2.76.3__tar.gz → 2.78.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.76.3/code_context_control.egg-info → code_context_control-2.78.0}/PKG-INFO +1 -1
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/_hook_utils.py +42 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/c3.py +1 -1
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/docs.html +9 -3
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_access_guard.py +47 -4
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_dispatch.py +132 -20
- {code_context_control-2.76.3 → code_context_control-2.78.0/code_context_control.egg-info}/PKG-INFO +1 -1
- {code_context_control-2.76.3 → code_context_control-2.78.0}/code_context_control.egg-info/SOURCES.txt +1 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/pyproject.toml +1 -1
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_access_guard_shell_project.py +12 -1
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hook_access_guard.py +93 -1
- code_context_control-2.78.0/tests/test_hook_codex_compat.py +500 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hook_dispatch.py +6 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/LICENSE +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/README.md +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/__init__.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/commands/__init__.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/commands/common.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/commands/parser.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/edits.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/guide/access.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/guide/bitbucket.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/guide/credentials.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/guide/getting-started.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/guide/index.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/guide/masking.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/guide/oracle.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/guide/shared.css +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/guide/tools.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/guide/workflow.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_artifact.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_auto_snapshot.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_c3_signal.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_c3read.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_edit_ledger.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_edit_unlock.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_filter.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_ghost_files.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_pretool_enforce.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_prompt_recall.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_read.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_session_stats.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hook_terse_advisor.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_server.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/app.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/add_project.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/config_editor.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/drill_health.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/drill_panel.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/drill_subprojects.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/drill_tasks.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/drill_views.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/global_search.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/hub_credentials.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/hub_enforcement.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/hub_locks.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/mcp_manager.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/modals.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/project_card.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/project_tree.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/session_drawer.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/settings_modal.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/sidebar.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/summary_bar.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/task_board.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/toasts.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/components/topbar.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui/state.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/hub_ui.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/mcp_proxy.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/mcp_server.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/progress.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/server.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/__init__.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/_grants.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/_helpers.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/agent.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/artifacts.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/bitbucket.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/compress.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/credentials.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/delegate.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/edit.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/edit_verify.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/edits.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/federate.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/filter.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/impact.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/jira.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/locks.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/memory.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/override.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/project.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/read.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/search.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/session.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/shell.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/status.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/tasks.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/tools/validate.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/api.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/app.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/access.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/bitbucket.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/chat.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/credentials.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/dashboard.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/edits.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/enforcement.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/instructions.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/jira.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/memory.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/sessions.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/settings.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/sidebar.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/components/tasks.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/icons.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/pm_shared.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/shared.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui/theme.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui_legacy.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/cli/ui_nano.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/code_context_control.egg-info/dependency_links.txt +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/code_context_control.egg-info/entry_points.txt +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/code_context_control.egg-info/requires.txt +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/code_context_control.egg-info/top_level.txt +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/core/__init__.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/core/config.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/core/ide.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/core/mcp_toml.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/core/web_security.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/__init__.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/config.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/mcp_oracle.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/oracle_server.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/oracle_ui.html +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/__init__.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/activity_reporter.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/api_auth.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/c3_bridge.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/chat_engine.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/chat_poll.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/chat_store.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/cross_memory.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/discovery_audit.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/federated_graph.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/health_checker.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/insight_engine.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/local_session.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/memory_reader.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/memory_writer.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/mobile_api.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/project_scanner.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/review_agent.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/tool_executor.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/services/tool_registry.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/activity.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/agents.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/app.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/busy.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/chat/conversations.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/chat/input.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/chat/markdown.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/chat/send.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/chat/stream_renderer.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/chat/toolbar.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/core.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/crossgraph.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/header.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/insights.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/projects.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/qrcode.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/settings.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/suggestions.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/oracle/ui/theme_tabs.js +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/__init__.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/access_guard.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/access_telemetry.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/activity_log.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/agent_base.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/agent_locks.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/agents.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/artifact_defs.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/artifact_store.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/auto_memory.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/bench/__init__.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/bench/external/__init__.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/bench/external/aider_polyglot.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/bench/external/swe_bench.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/benchmark_dashboard.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/bitbucket_client.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/bitbucket_credentials.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/circuit_breaker.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/claude_md.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/compressor.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/context_snapshot.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/conversation_store.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/credential_store.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/doc_index.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/e2e_benchmark.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/e2e_evaluator.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/e2e_tasks.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/edit_ledger.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/embedding_index.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/enforcement_policy.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/error_reporting.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/file_memory.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/git_context.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/hub_service.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/indexer.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/jira_client.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/jira_cloud.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/jira_credentials.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/jira_data_center.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/jira_links.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/mask_activation.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/mask_mirror.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/mask_presets.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/memory.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/memory_consolidator.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/memory_distiller.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/memory_graph.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/memory_grounder.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/memory_queue.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/memory_scorer.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/metrics.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/notifications.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/ollama_bridge.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/ollama_client.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/ollama_credentials.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/output_filter.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/override_grants.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/override_policy.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/override_requests.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/override_wake.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/parser.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/project_manager.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/project_runtime.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/protocol.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/proxy_state.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/repo_map.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/retention.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/retrieval_broker.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/router.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/runtime.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/scanner.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/session_benchmark.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/session_manager.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/session_preloader.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/subprojects.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/task_store.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/telemetry.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/text_index.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/time_tracker.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/tool_classifier.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/transcript_index.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/validation_cache.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/vector_store.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/watcher.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/services/win_subprocess.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/setup.cfg +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_access_builtin_optout.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_access_guard.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_access_guard_meta.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_access_guard_surfaces.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_access_guard_wiring.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_activity_reporter.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_agent_locks.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_aider_polyglot.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_antigravity_transition.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_artifact_store.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_artifact_tool.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_bitbucket_cli_smoke.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_bitbucket_client.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_bitbucket_config_fallback.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_bitbucket_credentials.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_bitbucket_tool.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_c3_shell.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_chat_poll.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_circuit_breaker.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_claude_md_merge.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_cli_smoke.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_compressor_large_file.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_credential_store.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_credentials_routes.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_credentials_tool.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_creds_cli_smoke.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_delegate_cascade.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_delegate_version_probe.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_e2e_benchmark.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_edit_ledger_deadline.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_edit_ledger_hook.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_edit_locking.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_edit_normalization.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_edit_verify.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_embedding_index_deadlock.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_enforcement_flip.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_enforcement_policy.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_enforcement_routes.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_federated_graph.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_filter_backoff.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_ghost_files.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_ghost_generation.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_git_branch_awareness.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hook_pretool_enforce.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hook_prompt_recall.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hook_smoke.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hook_state.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hook_utf8_stdio.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hub_credentials_route.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hub_credentials_routes_write.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hub_enforcement_routes.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hub_inspect_api.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hub_locks_routes.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hub_server_smoke.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_hub_subproject_links.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_ide_workflow_adaptation.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_init_scan.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_install_mcp_entrypoint.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_jira_cli_smoke.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_jira_client.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_jira_config_fallback.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_jira_credentials.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_jira_links.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_jira_routes.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_jira_tool.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_lazy_store_init.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_mask_guard.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_mask_routes.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_mask_surfaces.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_mcp_host_guard.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_mcp_memory_timeout.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_mcp_server_smoke.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_mcp_toml.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_memory_distiller.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_memory_graph_api.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_memory_queue.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_memory_system.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_mobile_api.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_mobile_extras.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_mobile_override_routes.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_mobile_security_api.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_no_stale_model_pins.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_notification_dedup.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_notification_discipline.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_ollama_credentials.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_api_auth.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_apikey_api.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_c3_bridge.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_chat_engine.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_chat_store.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_discovery_api.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_discovery_audit.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_local_auth.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_ollama_bridge.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_scanner_cache.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_security_fixes.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_session_bootstrap.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_subproject_awareness.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_oracle_ui_bundle.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_output_filter.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_override_grants.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_override_grants_mcp.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_override_requests.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_override_wake.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_permissions.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_pm_api.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_project_manager.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_project_manager_merge.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_project_tool.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_read_coercion.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_read_edit_parity.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_related_facts.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_repo_map.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_response_boilerplate.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_retention.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_review_digest.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_service_durability.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_session_benchmark.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_session_budget.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_shell_creds.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_shell_robustness.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_subproject_exclusion.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_subproject_federation.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_subprojects.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_swe_bench.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_task_store.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_task_tool.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_time_tracker.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_token_telemetry.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_tool_registry.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_upgrade_and_version.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_validate.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_vault_write_guard.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_version_sync.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_web_security.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_windows_reliability.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/__init__.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/backend.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/main.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/__init__.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/benchmark_view.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/claudemd_view.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/compress_view.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/index_view.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/init_view.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/mcp_view.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/optimize_view.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/pipe_view.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/projects_view.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/search_view.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/session_view.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/stats.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/screens/ui_view.py +0 -0
- {code_context_control-2.76.3 → code_context_control-2.78.0}/tui/theme.tcss +0 -0
{code_context_control-2.76.3/code_context_control.egg-info → code_context_control-2.78.0}/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.78.0
|
|
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
|
|
@@ -359,6 +359,48 @@ def record_unlocked_files(
|
|
|
359
359
|
save_enforcement_state(state, project_path)
|
|
360
360
|
|
|
361
361
|
|
|
362
|
+
# ── Host (IDE runtime) detection ─────────────────────────────────────────────
|
|
363
|
+
# Three hosts drive C3 hooks, and their OUTPUT contracts differ enough that one
|
|
364
|
+
# shared payload shape cannot satisfy all of them:
|
|
365
|
+
#
|
|
366
|
+
# claude — top-level "tool_result" / "additionalContext" accepted.
|
|
367
|
+
# gemini — no tool_result; context nests under hookSpecificOutput.
|
|
368
|
+
# codex — every hook output schema is additionalProperties:false and
|
|
369
|
+
# hookSpecificOutput.hookEventName is REQUIRED. Unknown keys are a
|
|
370
|
+
# hard deserialization error, not a warning, so emitting the Claude
|
|
371
|
+
# shape makes Codex discard the whole hook response.
|
|
372
|
+
HOST_CLAUDE = "claude"
|
|
373
|
+
HOST_GEMINI = "gemini"
|
|
374
|
+
HOST_CODEX = "codex"
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def detect_host(data: dict) -> str:
|
|
378
|
+
"""Identify which agent runtime sent this hook payload.
|
|
379
|
+
|
|
380
|
+
Codex is detected by `turn_id`, which its wire schema marks required on
|
|
381
|
+
every turn-scoped event C3 hooks (pre/post tool use, user prompt submit,
|
|
382
|
+
stop) and documents as "Codex extension". Claude Code never sends it.
|
|
383
|
+
|
|
384
|
+
Codex MUST be checked before Gemini: Codex declares `tool_response` as
|
|
385
|
+
free-form, so an MCP tool that returns an object would otherwise trip the
|
|
386
|
+
isinstance-dict test and be mistaken for Gemini.
|
|
387
|
+
|
|
388
|
+
The CODEX_* env fallback covers a future Codex that drops `turn_id`. A
|
|
389
|
+
false positive there degrades gracefully — Claude Code also accepts
|
|
390
|
+
hookSpecificOutput.additionalContext — so guessing "codex" is the safe
|
|
391
|
+
direction to be wrong in.
|
|
392
|
+
"""
|
|
393
|
+
if not isinstance(data, dict):
|
|
394
|
+
return HOST_CLAUDE
|
|
395
|
+
if data.get("turn_id"):
|
|
396
|
+
return HOST_CODEX
|
|
397
|
+
if isinstance(data.get("tool_response"), dict):
|
|
398
|
+
return HOST_GEMINI
|
|
399
|
+
if os.environ.get("CODEX_THREAD_ID") or os.environ.get("CODEX_MANAGED_BY_NPM"):
|
|
400
|
+
return HOST_CODEX
|
|
401
|
+
return HOST_CLAUDE
|
|
402
|
+
|
|
403
|
+
|
|
362
404
|
# Map Gemini CLI built-in tool names → canonical Claude Code equivalents
|
|
363
405
|
GEMINI_TOOL_MAP = {
|
|
364
406
|
"run_shell_command": "Bash",
|
|
@@ -1747,11 +1747,17 @@ Background agent system with periodic check loop and AI enhancement.
|
|
|
1747
1747
|
...</code></pre>
|
|
1748
1748
|
|
|
1749
1749
|
<!-- ─── Hooks ──────────────────────── -->
|
|
1750
|
-
<h3 id="hooks">PostToolUse Hooks <span class="badge badge-blue" style="font-size:10px">Claude Code
|
|
1750
|
+
<h3 id="hooks">PostToolUse Hooks <span class="badge badge-blue" style="font-size:10px">installed for Claude Code</span>
|
|
1751
1751
|
</h3>
|
|
1752
|
-
<p>C3 registers
|
|
1753
|
-
certain tools.
|
|
1752
|
+
<p>C3 registers its PostToolUse hooks in <code>.claude/settings.local.json</code> that run after Claude executes
|
|
1753
|
+
certain tools. C3 only <em>installs</em> them for Claude Code — other IDEs rely on the workflow
|
|
1754
1754
|
instructions in their instructions file instead.</p>
|
|
1755
|
+
<p>The dispatcher itself is not Claude-only. Since v2.78.0 it branches by host and speaks three wire formats:
|
|
1756
|
+
Claude Code, Gemini CLI, and Codex CLI — so a Codex session with a hand-authored
|
|
1757
|
+
<code>.codex/hooks.json</code> pointing at <code>cli/hook_dispatch.py</code> gets full enforcement and
|
|
1758
|
+
bookkeeping. Codex validates hook output strictly (<code>additionalProperties: false</code>), which is why it
|
|
1759
|
+
needs its own serializer rather than the Claude shape. Output filtering is skipped on Codex: its schema has no
|
|
1760
|
+
<code>tool_result</code> to replace, so tokenizing the payload would be wasted work.</p>
|
|
1755
1761
|
|
|
1756
1762
|
<table>
|
|
1757
1763
|
<tr>
|
|
@@ -10,6 +10,7 @@ Bash coverage is a best-effort, existence-gated token scan — advisory by
|
|
|
10
10
|
design (docs/access-guard.md §3/§6); it catches an agent naming a denied
|
|
11
11
|
path, not an adversary hiding one.
|
|
12
12
|
"""
|
|
13
|
+
import os
|
|
13
14
|
import re
|
|
14
15
|
import sys
|
|
15
16
|
from pathlib import Path
|
|
@@ -93,6 +94,14 @@ _GIT_CMD_RE = re.compile(r"^\s*(?:[\w.\-]*[/\\])?git(?:\.exe)?\b", re.IGNORECASE
|
|
|
93
94
|
# call arrives.
|
|
94
95
|
_SEGMENT_SPLIT = re.compile(r"&&|\|\||[;|\n]")
|
|
95
96
|
|
|
97
|
+
# `cd <dir>` — the segment that moves the ground under every later one. Handles
|
|
98
|
+
# a quoted target, since project paths on this platform contain spaces, and the
|
|
99
|
+
# Windows `/d` flag.
|
|
100
|
+
_CD_RE = re.compile(
|
|
101
|
+
r"""^\s*cd\s+(?:/d\s+)?(?P<dir>"[^"]+"|'[^']+'|\S+)\s*$""",
|
|
102
|
+
re.IGNORECASE,
|
|
103
|
+
)
|
|
104
|
+
|
|
96
105
|
|
|
97
106
|
def _is_network_token(tok: str) -> bool:
|
|
98
107
|
"""True for URLs and IPv6 literals/CIDRs — never for an ADS spelling."""
|
|
@@ -199,6 +208,20 @@ def _target(tool_input: dict) -> str:
|
|
|
199
208
|
)
|
|
200
209
|
|
|
201
210
|
|
|
211
|
+
def _cd_target(segment: str, cwd: str) -> str | None:
|
|
212
|
+
"""The directory a `cd` segment moves to, resolved against ``cwd``."""
|
|
213
|
+
m = _CD_RE.match(segment or "")
|
|
214
|
+
if not m:
|
|
215
|
+
return None
|
|
216
|
+
target = m.group("dir").strip("\"'")
|
|
217
|
+
if not target or target.startswith("-"):
|
|
218
|
+
return None
|
|
219
|
+
try:
|
|
220
|
+
return os.path.abspath(os.path.join(cwd, os.path.expanduser(target)))
|
|
221
|
+
except (OSError, ValueError):
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
|
|
202
225
|
def _scan_shell(cmd: str, base: str):
|
|
203
226
|
"""(denial, token) for the first confident deny-rule hit, else (None, '').
|
|
204
227
|
|
|
@@ -218,6 +241,7 @@ def _scan_shell(cmd: str, base: str):
|
|
|
218
241
|
not have its first token reinterpreted just because a later segment is git.
|
|
219
242
|
"""
|
|
220
243
|
budget = _MAX_TOKENS
|
|
244
|
+
cwd = base
|
|
221
245
|
for segment in _SEGMENT_SPLIT.split(cmd or ""):
|
|
222
246
|
if budget <= 0:
|
|
223
247
|
break
|
|
@@ -227,7 +251,10 @@ def _scan_shell(cmd: str, base: str):
|
|
|
227
251
|
tok = raw.strip("\"'`;,()")
|
|
228
252
|
if not tok or tok.startswith("-"):
|
|
229
253
|
continue
|
|
230
|
-
|
|
254
|
+
# A revspec's path half need not contain a separator — `HEAD:.env`
|
|
255
|
+
# has none, so it used to be dropped here before any rule saw it.
|
|
256
|
+
if ("/" not in tok and "\\" not in tok and not tok.startswith(".")
|
|
257
|
+
and not (is_git and _GIT_REVSPEC_RE.match(tok))):
|
|
231
258
|
continue
|
|
232
259
|
# Order matters only for readability: a URL is also syntax-free, so
|
|
233
260
|
# either check alone would skip 'https://x'. Both are kept because
|
|
@@ -249,11 +276,22 @@ def _scan_shell(cmd: str, base: str):
|
|
|
249
276
|
tok = revspec_path
|
|
250
277
|
if re.match(r"^/[a-z]/", tok): # MSYS /c/foo → C:/foo
|
|
251
278
|
tok = f"{tok[1]}:{tok[2:]}"
|
|
252
|
-
|
|
279
|
+
# Resolve against the cwd the command will actually run in, not the
|
|
280
|
+
# session root. `cd /elsewhere && cat .env` used to compute the
|
|
281
|
+
# denial correctly and then throw it away, because the existence
|
|
282
|
+
# gate looked for `.env` under the project root where it does not
|
|
283
|
+
# live (#82). The rule base stays `base` — policy is the project's;
|
|
284
|
+
# only the path being judged follows the shell.
|
|
285
|
+
probe = tok
|
|
286
|
+
if not os.path.isabs(probe):
|
|
287
|
+
try:
|
|
288
|
+
probe = os.path.join(cwd, probe)
|
|
289
|
+
except (OSError, ValueError):
|
|
290
|
+
probe = tok
|
|
291
|
+
denial = ag.check(probe, "read", base)
|
|
253
292
|
if denial and denial.kind == "deny":
|
|
254
293
|
try:
|
|
255
|
-
|
|
256
|
-
exists = (p if p.is_absolute() else Path(base) / p).exists()
|
|
294
|
+
exists = Path(probe).exists()
|
|
257
295
|
except OSError:
|
|
258
296
|
exists = False
|
|
259
297
|
# A revspec names a path in HISTORY, so the working tree is the
|
|
@@ -262,6 +300,11 @@ def _scan_shell(cmd: str, base: str):
|
|
|
262
300
|
# existence-gating it would be a hole this rewrite opened.
|
|
263
301
|
if exists or from_revspec or denial.rule.startswith("<"):
|
|
264
302
|
return denial, tok
|
|
303
|
+
# AFTER the segment's own tokens: `cd x` moves the ground for what
|
|
304
|
+
# follows, not for its own argument.
|
|
305
|
+
moved = _cd_target(segment, cwd)
|
|
306
|
+
if moved is not None:
|
|
307
|
+
cwd = moved
|
|
265
308
|
return None, ""
|
|
266
309
|
|
|
267
310
|
|
|
@@ -14,12 +14,20 @@ Matcher granularity that previously lived in .claude/settings.local.json
|
|
|
14
14
|
routing tables below — install-mcp registers the dispatcher once per matcher,
|
|
15
15
|
and the dispatcher decides which sub-hook logic applies.
|
|
16
16
|
|
|
17
|
-
Output composition
|
|
17
|
+
Output composition merges sub-hook results the same way for every host:
|
|
18
18
|
- a permissionDecision "deny" from any sub-hook wins over allows;
|
|
19
19
|
- additionalContext strings from all sub-hooks are concatenated;
|
|
20
|
-
- a tool_result replacement (hook_filter) is
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
- a tool_result replacement (hook_filter) is captured.
|
|
21
|
+
|
|
22
|
+
Serialization then branches by host, because the three runtimes do NOT share
|
|
23
|
+
a wire contract (see _hook_utils.detect_host):
|
|
24
|
+
- Claude Code — top-level tool_result / additionalContext; plain-text
|
|
25
|
+
messages print raw when no structured JSON output exists;
|
|
26
|
+
- Gemini CLI — no tool_result; context nests under hookSpecificOutput;
|
|
27
|
+
- Codex CLI — every hook output schema is additionalProperties:false with a
|
|
28
|
+
required hookEventName, so output is built from a per-event whitelist and
|
|
29
|
+
Codex never sees a tool_result. Codex also skips hook_filter entirely:
|
|
30
|
+
it is the only sub-hook that tokenizes the full tool payload.
|
|
23
31
|
|
|
24
32
|
Failure visibility: a sub-hook crash is logged to .c3/hook_errors.log and
|
|
25
33
|
never kills the remaining sub-hooks. CRITICAL failures — a sub-hook module
|
|
@@ -44,10 +52,40 @@ from cli import _hook_utils # noqa: E402
|
|
|
44
52
|
# spellings share ONE module instance (state warnings must not split).
|
|
45
53
|
sys.modules.setdefault("_hook_utils", _hook_utils)
|
|
46
54
|
|
|
47
|
-
from cli._hook_utils import
|
|
55
|
+
from cli._hook_utils import ( # noqa: E402
|
|
56
|
+
HOST_CLAUDE,
|
|
57
|
+
HOST_CODEX,
|
|
58
|
+
HOST_GEMINI,
|
|
59
|
+
detect_host,
|
|
60
|
+
log_hook_error,
|
|
61
|
+
normalize_tool_name,
|
|
62
|
+
)
|
|
48
63
|
|
|
49
64
|
VALID_EVENTS = ("pretool", "posttool", "stop", "prompt")
|
|
50
65
|
|
|
66
|
+
# ── Codex wire contract ─────────────────────────────────────────────────────
|
|
67
|
+
# Codex validates hook output against additionalProperties:false schemas, so
|
|
68
|
+
# the Codex branch emits by WHITELIST rather than by "drop the keys we know
|
|
69
|
+
# about". A sub-hook that invents a new output key can then never break Codex
|
|
70
|
+
# — the key is simply not forwarded.
|
|
71
|
+
_CODEX_EVENT_NAMES = {
|
|
72
|
+
"pretool": "PreToolUse",
|
|
73
|
+
"posttool": "PostToolUse",
|
|
74
|
+
"prompt": "UserPromptSubmit",
|
|
75
|
+
"stop": "Stop",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Keys each event's hookSpecificOutput accepts. "stop" is absent deliberately:
|
|
79
|
+
# stop.command.output has no hookSpecificOutput member at all.
|
|
80
|
+
_CODEX_HSO_KEYS = {
|
|
81
|
+
"pretool": {
|
|
82
|
+
"hookEventName", "additionalContext",
|
|
83
|
+
"permissionDecision", "permissionDecisionReason", "updatedInput",
|
|
84
|
+
},
|
|
85
|
+
"posttool": {"hookEventName", "additionalContext", "updatedMCPToolOutput"},
|
|
86
|
+
"prompt": {"hookEventName", "additionalContext"},
|
|
87
|
+
}
|
|
88
|
+
|
|
51
89
|
# ── Routing tables (parity with the pre-v2.42 per-matcher registration) ─────
|
|
52
90
|
|
|
53
91
|
# c3 tools whose completion refreshes the enforcement signal — exactly the
|
|
@@ -77,9 +115,15 @@ _GHOST_TOOLS = {
|
|
|
77
115
|
}
|
|
78
116
|
|
|
79
117
|
|
|
80
|
-
def _routes(event: str, raw_tool: str, norm_tool: str):
|
|
118
|
+
def _routes(event: str, raw_tool: str, norm_tool: str, host: str = HOST_CLAUDE):
|
|
81
119
|
"""Yield sub-hook module names applicable to this event + tool, in the
|
|
82
|
-
same relative order the separate hook commands used to run.
|
|
120
|
+
same relative order the separate hook commands used to run.
|
|
121
|
+
|
|
122
|
+
`host` gates hooks that cannot pay their way on a given runtime — see the
|
|
123
|
+
hook_filter carve-out below. Every other sub-hook is host-agnostic: they
|
|
124
|
+
do bookkeeping (ledger, artifact, signal, unlock, ghost sweep) without
|
|
125
|
+
parsing or tokenizing the tool payload, so they stay on for all hosts.
|
|
126
|
+
"""
|
|
83
127
|
if event == "pretool":
|
|
84
128
|
# Access Guard runs FIRST: merge_outputs keeps the first deny, so a
|
|
85
129
|
# policy denial here cannot be readmitted by sticky-unlock allows.
|
|
@@ -87,7 +131,13 @@ def _routes(event: str, raw_tool: str, norm_tool: str):
|
|
|
87
131
|
# hook_pretool_enforce self-filters via its _PREREQS table.
|
|
88
132
|
yield "hook_pretool_enforce"
|
|
89
133
|
elif event == "posttool":
|
|
90
|
-
|
|
134
|
+
# hook_filter is the ONE sub-hook that reads the whole tool payload:
|
|
135
|
+
# it tiktoken-encodes the entire Bash output to measure savings, a
|
|
136
|
+
# Rust-side allocation proportional to output size. On Codex that work
|
|
137
|
+
# is also pointless — its schema has no tool_result, so the filtered
|
|
138
|
+
# text could never replace the output anyway. Skip it entirely rather
|
|
139
|
+
# than risk an allocation failure for a result we must discard.
|
|
140
|
+
if norm_tool == "Bash" and host != HOST_CODEX:
|
|
91
141
|
yield "hook_filter"
|
|
92
142
|
if norm_tool == "Read":
|
|
93
143
|
yield "hook_read"
|
|
@@ -155,18 +205,66 @@ def _load_run(module_name: str):
|
|
|
155
205
|
return result
|
|
156
206
|
|
|
157
207
|
|
|
208
|
+
def _codex_output(event: str, deny_hso, contexts: list, tool_result,
|
|
209
|
+
texts: list) -> dict | None:
|
|
210
|
+
"""Serialize merged sub-hook results into Codex's wire contract.
|
|
211
|
+
|
|
212
|
+
Codex accepts neither a top-level `tool_result` nor a top-level
|
|
213
|
+
`additionalContext`, and requires `hookEventName` inside
|
|
214
|
+
hookSpecificOutput. Anything that has no Codex-legal home is folded into a
|
|
215
|
+
channel that does exist rather than dropped:
|
|
216
|
+
|
|
217
|
+
- a tool_result replacement becomes leading context (it cannot replace the
|
|
218
|
+
output on Codex, but the filtered text is still worth reading);
|
|
219
|
+
- "_text" becomes `systemMessage`, the only user-visible string field
|
|
220
|
+
present on every Codex hook output schema — including Stop, which has no
|
|
221
|
+
hookSpecificOutput at all, so its context lands there too.
|
|
222
|
+
"""
|
|
223
|
+
if tool_result is not None:
|
|
224
|
+
contexts.insert(0, str(tool_result))
|
|
225
|
+
|
|
226
|
+
result: dict = {}
|
|
227
|
+
hso_keys = _CODEX_HSO_KEYS.get(event)
|
|
228
|
+
joined = "\n".join(c for c in contexts if c)
|
|
229
|
+
|
|
230
|
+
if hso_keys is not None:
|
|
231
|
+
hso = {k: v for k, v in (deny_hso or {}).items() if k in hso_keys}
|
|
232
|
+
if joined:
|
|
233
|
+
# A deny's own reason travels in permissionDecisionReason; `joined`
|
|
234
|
+
# is the non-deny context, so the two never collide.
|
|
235
|
+
hso["additionalContext"] = joined
|
|
236
|
+
if hso:
|
|
237
|
+
hso["hookEventName"] = _CODEX_EVENT_NAMES[event]
|
|
238
|
+
result["hookSpecificOutput"] = hso
|
|
239
|
+
elif joined:
|
|
240
|
+
texts.insert(0, joined)
|
|
241
|
+
|
|
242
|
+
if texts:
|
|
243
|
+
result["systemMessage"] = "\n".join(texts)
|
|
244
|
+
return result or None
|
|
245
|
+
|
|
246
|
+
|
|
158
247
|
def merge_outputs(outputs: list, warnings: list, is_gemini: bool = False,
|
|
159
|
-
event: str = "") -> dict | None:
|
|
160
|
-
"""Compose sub-hook outputs
|
|
248
|
+
event: str = "", host: str | None = None) -> dict | None:
|
|
249
|
+
"""Compose sub-hook outputs into ONE host-appropriate hook response.
|
|
161
250
|
|
|
251
|
+
Collection is host-agnostic:
|
|
162
252
|
- deny beats allow: the first hookSpecificOutput carrying a
|
|
163
|
-
permissionDecision "deny" is kept
|
|
253
|
+
permissionDecision "deny" is kept;
|
|
164
254
|
- additionalContext strings are concatenated with newlines
|
|
165
255
|
(critical warnings appended last);
|
|
166
|
-
- tool_result replacement (hook_filter) is
|
|
167
|
-
|
|
168
|
-
|
|
256
|
+
- a tool_result replacement (hook_filter) is captured.
|
|
257
|
+
|
|
258
|
+
Emission is not. Each host gets the shape its parser accepts — Claude
|
|
259
|
+
takes top-level keys, Gemini nests context and has no tool_result, Codex
|
|
260
|
+
validates against additionalProperties:false schemas (see _codex_output).
|
|
261
|
+
|
|
262
|
+
`is_gemini` is retained for callers that predate `host`; `host` wins when
|
|
263
|
+
both are given.
|
|
169
264
|
"""
|
|
265
|
+
if host is None:
|
|
266
|
+
host = HOST_GEMINI if is_gemini else HOST_CLAUDE
|
|
267
|
+
|
|
170
268
|
deny_hso = None
|
|
171
269
|
contexts: list = []
|
|
172
270
|
texts: list = []
|
|
@@ -192,6 +290,10 @@ def merge_outputs(outputs: list, warnings: list, is_gemini: bool = False,
|
|
|
192
290
|
|
|
193
291
|
contexts.extend(warnings)
|
|
194
292
|
|
|
293
|
+
if host == HOST_CODEX:
|
|
294
|
+
return _codex_output(event, deny_hso, contexts, tool_result, texts)
|
|
295
|
+
|
|
296
|
+
is_gemini = host == HOST_GEMINI
|
|
195
297
|
result: dict = {}
|
|
196
298
|
if deny_hso is not None:
|
|
197
299
|
result["hookSpecificOutput"] = deny_hso
|
|
@@ -229,14 +331,14 @@ def dispatch(event: str, payload: dict, project_path: Path | None = None) -> dic
|
|
|
229
331
|
"""Run all applicable sub-hooks in-process and merge their outputs."""
|
|
230
332
|
raw_tool = str(payload.get("tool_name", ""))
|
|
231
333
|
norm_tool = normalize_tool_name(raw_tool)
|
|
232
|
-
|
|
334
|
+
host = detect_host(payload)
|
|
233
335
|
|
|
234
336
|
outputs: list = []
|
|
235
337
|
warnings: list = []
|
|
236
338
|
# Drain any stale warnings from a previous dispatch in this process.
|
|
237
339
|
_hook_utils.drain_state_warnings()
|
|
238
340
|
|
|
239
|
-
for module_name in _routes(event, raw_tool, norm_tool):
|
|
341
|
+
for module_name in _routes(event, raw_tool, norm_tool, host):
|
|
240
342
|
run_fn, err = _load_run(module_name)
|
|
241
343
|
if run_fn is None:
|
|
242
344
|
warnings.append(
|
|
@@ -263,7 +365,7 @@ def dispatch(event: str, payload: dict, project_path: Path | None = None) -> dic
|
|
|
263
365
|
# become visible instead of silently disabling enforcement.
|
|
264
366
|
warnings.extend(_hook_utils.drain_state_warnings())
|
|
265
367
|
|
|
266
|
-
return merge_outputs(outputs, warnings,
|
|
368
|
+
return merge_outputs(outputs, warnings, event=event, host=host)
|
|
267
369
|
|
|
268
370
|
|
|
269
371
|
def main() -> None:
|
|
@@ -288,14 +390,24 @@ def main() -> None:
|
|
|
288
390
|
try:
|
|
289
391
|
output = dispatch(event, payload)
|
|
290
392
|
except Exception as exc:
|
|
291
|
-
# The dispatcher itself failing is critical — say so.
|
|
393
|
+
# The dispatcher itself failing is critical — say so. Route the notice
|
|
394
|
+
# through merge_outputs so even the panic path speaks the host's
|
|
395
|
+
# dialect: a raw {"additionalContext": ...} is itself invalid on Codex,
|
|
396
|
+
# which would turn one crash into two error reports.
|
|
292
397
|
log_hook_error("hook_dispatch", exc)
|
|
293
|
-
|
|
398
|
+
notice = {
|
|
294
399
|
"additionalContext": (
|
|
295
400
|
f"[c3:hook-error] hook_dispatch: {type(exc).__name__}: {exc}; "
|
|
296
401
|
"see .c3/hook_errors.log"
|
|
297
402
|
)
|
|
298
|
-
}
|
|
403
|
+
}
|
|
404
|
+
try:
|
|
405
|
+
output = merge_outputs([notice], [], event=event,
|
|
406
|
+
host=detect_host(payload))
|
|
407
|
+
except Exception:
|
|
408
|
+
output = notice
|
|
409
|
+
if output:
|
|
410
|
+
print(json.dumps(output))
|
|
299
411
|
return
|
|
300
412
|
|
|
301
413
|
if not output:
|
{code_context_control-2.76.3 → code_context_control-2.78.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.78.0
|
|
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
|
|
@@ -301,6 +301,7 @@ tests/test_ghost_files.py
|
|
|
301
301
|
tests/test_ghost_generation.py
|
|
302
302
|
tests/test_git_branch_awareness.py
|
|
303
303
|
tests/test_hook_access_guard.py
|
|
304
|
+
tests/test_hook_codex_compat.py
|
|
304
305
|
tests/test_hook_dispatch.py
|
|
305
306
|
tests/test_hook_pretool_enforce.py
|
|
306
307
|
tests/test_hook_prompt_recall.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.78.0"
|
|
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"
|
{code_context_control-2.76.3 → code_context_control-2.78.0}/tests/test_access_guard_shell_project.py
RENAMED
|
@@ -75,6 +75,14 @@ class GuardBase(unittest.TestCase):
|
|
|
75
75
|
(self.proj / ".c3").mkdir()
|
|
76
76
|
self._gb = mock.patch.object(ag, "_global_base", return_value=None)
|
|
77
77
|
self._gb.start()
|
|
78
|
+
# delegate's response cache is a module global that outlives a test.
|
|
79
|
+
# The codex cases below key their task on id(svc), and CPython reuses
|
|
80
|
+
# an address once the previous svc is collected — so the second test
|
|
81
|
+
# could hit the FIRST one's cached "CODEX-OUT", return without ever
|
|
82
|
+
# calling the mocked _run_codex, and assert against an empty capture
|
|
83
|
+
# (`sandbox` is None, not the pinned value). That is allocator luck,
|
|
84
|
+
# which is why it only ever failed on some runners.
|
|
85
|
+
delegate_mod._delegate_cache.clear()
|
|
78
86
|
|
|
79
87
|
def tearDown(self):
|
|
80
88
|
self._gb.stop()
|
|
@@ -354,8 +362,11 @@ class TestDelegateGuard(GuardBase):
|
|
|
354
362
|
mock.patch.object(delegate_mod, "_codex_available", True), \
|
|
355
363
|
mock.patch.object(delegate_mod, "_codex_memory_bridge",
|
|
356
364
|
return_value=None):
|
|
365
|
+
# Key the task on the test name, not id(svc): an address is
|
|
366
|
+
# reused as soon as the previous svc is collected, which made two
|
|
367
|
+
# different tests share one cache entry.
|
|
357
368
|
out = delegate_mod.handle_delegate(
|
|
358
|
-
f"t-codex-{id(
|
|
369
|
+
f"t-codex-{self.id()}", "ask", "", "", svc, _finalize,
|
|
359
370
|
backend="codex")
|
|
360
371
|
return out, seen
|
|
361
372
|
|
|
@@ -342,10 +342,102 @@ class TestShellScanSyntaxTokens(HookGuardBase):
|
|
|
342
342
|
|
|
343
343
|
def test_a_denied_revspec_is_still_caught_after_a_separator(self):
|
|
344
344
|
denial, tok = hag._scan_shell(
|
|
345
|
-
|
|
345
|
+
f'cd "{self.proj}" && git show HEAD:secrets/key.txt', str(self.proj))
|
|
346
346
|
self.assertIsNotNone(denial)
|
|
347
347
|
self.assertEqual(tok, "secrets/key.txt")
|
|
348
348
|
|
|
349
|
+
def test_a_revspec_path_without_a_separator_now_reaches_the_rules(self):
|
|
350
|
+
"""`HEAD:.env` — no slash, so the token gate used to drop it (#82).
|
|
351
|
+
|
|
352
|
+
It also never reached the ADS check, which is the claim #81's body got
|
|
353
|
+
wrong. It reaches the rules now, and the revspec bypass of the existence
|
|
354
|
+
gate is what makes the verdict right: a blob in history is denied
|
|
355
|
+
whether or not that file is in the working tree today.
|
|
356
|
+
"""
|
|
357
|
+
denial, tok = hag._scan_shell(
|
|
358
|
+
"git show HEAD:.env", str(self.proj))
|
|
359
|
+
self.assertIsNotNone(denial)
|
|
360
|
+
self.assertEqual(tok, ".env")
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class TestShellScanFollowsCd(HookGuardBase):
|
|
364
|
+
"""#82 — the scan judged paths against the session root, not the cwd.
|
|
365
|
+
|
|
366
|
+
`cd <elsewhere> && cat .env` computed the denial correctly and then threw it
|
|
367
|
+
away, because the existence gate looked for `.env` under the project root
|
|
368
|
+
where it does not live. The rule was never wrong; the question was asked
|
|
369
|
+
about the wrong directory.
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
def test_a_denied_file_read_after_cd_is_caught(self):
|
|
373
|
+
"""The reported case, reproduced with the rule that made it reachable.
|
|
374
|
+
|
|
375
|
+
Session rooted in one directory, command `cd`-ing to another, and a
|
|
376
|
+
builtin `**/.env*` deny — builtin because it binds on any path, which is
|
|
377
|
+
why the live failure was a secret file rather than a project-scoped one.
|
|
378
|
+
Before this fix the denial was computed and then discarded, because the
|
|
379
|
+
existence gate looked under the session root.
|
|
380
|
+
"""
|
|
381
|
+
(self.proj / ".env").write_text("SECRET=x", encoding="utf-8")
|
|
382
|
+
denial, _ = hag._scan_shell(
|
|
383
|
+
f'cd "{self.proj}" && cat .env', str(self.proj.parent))
|
|
384
|
+
self.assertIsNotNone(denial)
|
|
385
|
+
self.assertEqual(denial.rule, "**/.env*")
|
|
386
|
+
|
|
387
|
+
def test_cd_out_then_relative_back_in_is_caught(self):
|
|
388
|
+
denial, _ = hag._scan_shell(
|
|
389
|
+
f'cd "{self.proj.parent}" && cat {self.proj.name}/secrets/key.txt',
|
|
390
|
+
str(self.proj))
|
|
391
|
+
self.assertIsNotNone(denial)
|
|
392
|
+
|
|
393
|
+
def test_cd_out_then_absolute_back_in_is_caught(self):
|
|
394
|
+
denial, _ = hag._scan_shell(
|
|
395
|
+
f'cd "{self.proj.parent}" && cat "{self.proj / "secrets/key.txt"}"',
|
|
396
|
+
str(self.proj))
|
|
397
|
+
self.assertIsNotNone(denial)
|
|
398
|
+
|
|
399
|
+
def test_the_existence_gate_still_applies_at_the_new_cwd(self):
|
|
400
|
+
"""Following the shell must not mean flagging files that are not there.
|
|
401
|
+
|
|
402
|
+
A `cd` into a directory without the file leaves nothing to read, and a
|
|
403
|
+
deny on nothing is the false positive this scanner keeps being fixed for.
|
|
404
|
+
"""
|
|
405
|
+
denial, _ = hag._scan_shell(
|
|
406
|
+
f'cd "{self.proj.parent}" && cat secrets/key.txt', str(self.proj))
|
|
407
|
+
self.assertIsNone(denial)
|
|
408
|
+
|
|
409
|
+
def test_cd_moves_only_the_segments_after_it(self):
|
|
410
|
+
"""`cat secrets/key.txt && cd /elsewhere` reads before it moves."""
|
|
411
|
+
denial, _ = hag._scan_shell(
|
|
412
|
+
"cat secrets/key.txt && cd /tmp", str(self.proj))
|
|
413
|
+
self.assertIsNotNone(denial)
|
|
414
|
+
|
|
415
|
+
def test_a_quoted_cd_target_with_spaces_is_parsed(self):
|
|
416
|
+
spaced = self.proj / "a dir"
|
|
417
|
+
spaced.mkdir()
|
|
418
|
+
denial, _ = hag._scan_shell(
|
|
419
|
+
f'cd "{spaced}" && cat ../secrets/key.txt', str(self.proj))
|
|
420
|
+
self.assertIsNotNone(denial)
|
|
421
|
+
|
|
422
|
+
def test_the_cd_target_itself_is_still_scanned(self):
|
|
423
|
+
"""`cd` into a denied directory is itself a token worth refusing."""
|
|
424
|
+
denial, tok = hag._scan_shell(
|
|
425
|
+
f'cd "{self.proj / "secrets"}"', str(self.proj))
|
|
426
|
+
self.assertIsNotNone(denial)
|
|
427
|
+
|
|
428
|
+
def test_a_bare_filename_is_still_out_of_scope(self):
|
|
429
|
+
"""Stated rather than implied: the token gate is unchanged.
|
|
430
|
+
|
|
431
|
+
`cd <denied dir> && type key.txt` is caught only incidentally, by the
|
|
432
|
+
`cd` argument. A separator-free filename still never reaches the rules,
|
|
433
|
+
which is a known limit of the token gate and not what #82 was about.
|
|
434
|
+
"""
|
|
435
|
+
self.assertIsNone(hag._cd_target("type key.txt", str(self.proj)))
|
|
436
|
+
denial, tok = hag._scan_shell(
|
|
437
|
+
f'cd "{self.proj / "secrets"}" && type key.txt', str(self.proj))
|
|
438
|
+
self.assertIsNotNone(denial)
|
|
439
|
+
self.assertNotIn("key.txt", tok)
|
|
440
|
+
|
|
349
441
|
def test_a_git_segment_does_not_license_rewrites_in_other_segments(self):
|
|
350
442
|
"""Per-segment, not per-string: `cat a:b && git status` is two commands.
|
|
351
443
|
|