code-context-control 2.71.0__tar.gz → 2.74.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.71.0/code_context_control.egg-info → code_context_control-2.74.0}/PKG-INFO +1 -1
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/c3.py +1 -1
- code_context_control-2.74.0/cli/tools/_grants.py +61 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/compress.py +4 -1
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/edit.py +10 -12
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/filter.py +4 -1
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/impact.py +3 -1
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/locks.py +4 -7
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/override.py +3 -4
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/read.py +8 -1
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/validate.py +5 -2
- {code_context_control-2.71.0 → code_context_control-2.74.0/code_context_control.egg-info}/PKG-INFO +1 -1
- {code_context_control-2.71.0 → code_context_control-2.74.0}/code_context_control.egg-info/SOURCES.txt +4 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/mobile_api.py +132 -8
- {code_context_control-2.71.0 → code_context_control-2.74.0}/pyproject.toml +1 -1
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/access_guard.py +38 -10
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/notifications.py +22 -1
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/override_policy.py +31 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/override_requests.py +30 -0
- code_context_control-2.74.0/services/override_wake.py +241 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_mobile_api.py +113 -1
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_mobile_override_routes.py +39 -0
- code_context_control-2.74.0/tests/test_override_grants_mcp.py +189 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_override_requests.py +125 -0
- code_context_control-2.74.0/tests/test_override_wake.py +278 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/LICENSE +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/README.md +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/__init__.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/_hook_utils.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/commands/__init__.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/commands/common.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/commands/parser.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/docs.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/edits.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/guide/access.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/guide/bitbucket.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/guide/credentials.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/guide/getting-started.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/guide/index.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/guide/masking.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/guide/oracle.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/guide/shared.css +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/guide/tools.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/guide/workflow.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_access_guard.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_artifact.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_auto_snapshot.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_c3_signal.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_c3read.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_dispatch.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_edit_ledger.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_edit_unlock.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_filter.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_ghost_files.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_pretool_enforce.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_prompt_recall.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_read.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_session_stats.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hook_terse_advisor.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_server.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/app.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/add_project.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/config_editor.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/drill_health.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/drill_panel.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/drill_subprojects.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/drill_tasks.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/drill_views.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/global_search.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/hub_credentials.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/hub_enforcement.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/hub_locks.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/mcp_manager.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/modals.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/project_card.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/project_tree.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/session_drawer.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/settings_modal.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/sidebar.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/summary_bar.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/task_board.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/toasts.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/components/topbar.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui/state.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/hub_ui.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/mcp_proxy.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/mcp_server.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/progress.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/server.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/__init__.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/_helpers.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/agent.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/artifacts.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/bitbucket.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/credentials.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/delegate.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/edits.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/federate.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/jira.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/memory.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/project.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/search.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/session.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/shell.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/status.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/tools/tasks.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/api.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/app.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/access.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/bitbucket.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/chat.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/credentials.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/dashboard.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/edits.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/enforcement.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/instructions.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/jira.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/memory.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/sessions.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/settings.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/sidebar.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/components/tasks.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/icons.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/pm_shared.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/shared.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui/theme.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui_legacy.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/cli/ui_nano.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/code_context_control.egg-info/dependency_links.txt +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/code_context_control.egg-info/entry_points.txt +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/code_context_control.egg-info/requires.txt +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/code_context_control.egg-info/top_level.txt +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/core/__init__.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/core/config.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/core/ide.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/core/mcp_toml.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/core/web_security.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/__init__.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/config.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/mcp_oracle.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/oracle_server.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/oracle_ui.html +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/__init__.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/activity_reporter.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/api_auth.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/c3_bridge.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/chat_engine.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/chat_store.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/cross_memory.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/discovery_audit.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/federated_graph.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/health_checker.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/insight_engine.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/local_session.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/memory_reader.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/memory_writer.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/project_scanner.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/review_agent.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/tool_executor.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/services/tool_registry.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/activity.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/agents.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/app.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/busy.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/chat/conversations.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/chat/input.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/chat/markdown.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/chat/send.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/chat/stream_renderer.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/chat/toolbar.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/core.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/crossgraph.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/header.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/insights.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/projects.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/qrcode.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/settings.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/suggestions.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/oracle/ui/theme_tabs.js +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/__init__.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/access_telemetry.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/activity_log.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/agent_base.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/agent_locks.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/agents.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/artifact_defs.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/artifact_store.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/auto_memory.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/bench/__init__.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/bench/external/__init__.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/bench/external/aider_polyglot.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/bench/external/swe_bench.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/benchmark_dashboard.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/bitbucket_client.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/bitbucket_credentials.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/circuit_breaker.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/claude_md.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/compressor.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/context_snapshot.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/conversation_store.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/credential_store.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/doc_index.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/e2e_benchmark.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/e2e_evaluator.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/e2e_tasks.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/edit_ledger.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/embedding_index.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/enforcement_policy.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/error_reporting.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/file_memory.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/git_context.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/hub_service.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/indexer.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/jira_client.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/jira_cloud.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/jira_credentials.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/jira_data_center.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/jira_links.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/mask_activation.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/mask_mirror.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/mask_presets.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/memory.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/memory_consolidator.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/memory_distiller.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/memory_graph.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/memory_grounder.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/memory_queue.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/memory_scorer.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/metrics.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/ollama_bridge.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/ollama_client.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/ollama_credentials.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/output_filter.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/override_grants.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/parser.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/project_manager.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/project_runtime.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/protocol.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/proxy_state.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/repo_map.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/retention.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/retrieval_broker.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/router.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/runtime.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/scanner.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/session_benchmark.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/session_manager.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/session_preloader.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/subprojects.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/task_store.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/telemetry.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/text_index.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/time_tracker.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/tool_classifier.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/transcript_index.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/validation_cache.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/vector_store.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/watcher.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/services/win_subprocess.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/setup.cfg +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_access_builtin_optout.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_access_guard.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_access_guard_meta.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_access_guard_shell_project.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_access_guard_surfaces.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_access_guard_wiring.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_activity_reporter.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_agent_locks.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_aider_polyglot.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_antigravity_transition.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_artifact_store.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_artifact_tool.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_bitbucket_cli_smoke.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_bitbucket_client.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_bitbucket_config_fallback.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_bitbucket_credentials.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_bitbucket_tool.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_c3_shell.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_circuit_breaker.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_claude_md_merge.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_cli_smoke.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_compressor_large_file.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_credential_store.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_credentials_routes.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_credentials_tool.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_creds_cli_smoke.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_delegate_cascade.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_delegate_version_probe.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_e2e_benchmark.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_edit_ledger_hook.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_edit_locking.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_edit_normalization.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_embedding_index_deadlock.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_enforcement_flip.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_enforcement_policy.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_enforcement_routes.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_federated_graph.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_filter_backoff.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_ghost_files.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_ghost_generation.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_git_branch_awareness.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hook_access_guard.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hook_dispatch.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hook_pretool_enforce.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hook_prompt_recall.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hook_smoke.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hook_state.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hook_utf8_stdio.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hub_credentials_route.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hub_credentials_routes_write.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hub_enforcement_routes.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hub_inspect_api.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hub_locks_routes.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hub_server_smoke.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_hub_subproject_links.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_ide_workflow_adaptation.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_init_scan.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_install_mcp_entrypoint.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_jira_cli_smoke.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_jira_client.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_jira_config_fallback.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_jira_credentials.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_jira_links.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_jira_routes.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_jira_tool.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_lazy_store_init.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_mask_guard.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_mask_routes.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_mask_surfaces.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_mcp_host_guard.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_mcp_memory_timeout.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_mcp_server_smoke.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_mcp_toml.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_memory_distiller.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_memory_graph_api.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_memory_queue.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_memory_system.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_mobile_security_api.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_no_stale_model_pins.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_notification_dedup.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_notification_discipline.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_ollama_credentials.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_api_auth.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_apikey_api.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_c3_bridge.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_chat_engine.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_chat_store.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_discovery_api.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_discovery_audit.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_local_auth.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_ollama_bridge.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_scanner_cache.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_security_fixes.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_session_bootstrap.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_subproject_awareness.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_oracle_ui_bundle.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_output_filter.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_override_grants.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_permissions.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_pm_api.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_project_manager.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_project_manager_merge.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_project_tool.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_read_coercion.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_read_edit_parity.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_related_facts.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_repo_map.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_response_boilerplate.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_retention.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_review_digest.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_service_durability.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_session_benchmark.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_session_budget.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_shell_creds.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_shell_robustness.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_subproject_exclusion.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_subproject_federation.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_subprojects.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_swe_bench.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_task_store.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_task_tool.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_time_tracker.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_token_telemetry.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_tool_registry.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_upgrade_and_version.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_validate.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_vault_write_guard.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_version_sync.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_web_security.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tests/test_windows_reliability.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/__init__.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/backend.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/main.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/__init__.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/benchmark_view.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/claudemd_view.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/compress_view.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/index_view.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/init_view.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/mcp_view.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/optimize_view.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/pipe_view.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/projects_view.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/search_view.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/session_view.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/stats.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/screens/ui_view.py +0 -0
- {code_context_control-2.71.0 → code_context_control-2.74.0}/tui/theme.tcss +0 -0
{code_context_control-2.71.0/code_context_control.egg-info → code_context_control-2.74.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.74.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
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Override grants on the MCP content surface (override-requests.md P2a).
|
|
2
|
+
|
|
3
|
+
The hooks have consulted grants since P1. The `c3_*` content tools have not:
|
|
4
|
+
they called the evaluator and refused without ever asking whether the user
|
|
5
|
+
had already approved this exact call. §13's coverage matrix recorded that as
|
|
6
|
+
"not yet — P2a", which meant an approved request did nothing for any agent
|
|
7
|
+
working through MCP — the grant was minted, and then no one looked at it.
|
|
8
|
+
|
|
9
|
+
This module is the single place those tools ask. It is deliberately thin:
|
|
10
|
+
all the policy ordering, TTL clamping, use accounting and audit lives in
|
|
11
|
+
`services.override_grants.gate_access`, which is the same call the hooks
|
|
12
|
+
make. Two implementations of "may I" is how the two surfaces drift apart.
|
|
13
|
+
|
|
14
|
+
`session_id` lives here for the same reason. It was duplicated in edit.py,
|
|
15
|
+
locks.py and override.py, each carrying a comment saying it must match the
|
|
16
|
+
others exactly — and P2a makes that invariant load-bearing rather than
|
|
17
|
+
merely tidy: a grant is minted under the session id `c3_override` computed
|
|
18
|
+
and consumed under the one `c3_edit` computes. If those two ever disagree,
|
|
19
|
+
every approval silently fails to apply.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def session_id(svc) -> str:
|
|
26
|
+
"""This agent's identity, for leases and for grants.
|
|
27
|
+
|
|
28
|
+
Falls back to the process id, never to "". Two agents that both resolved
|
|
29
|
+
to "" would count as ONE session and stop blocking each other — the exact
|
|
30
|
+
opposite of what a lock is for. Each Claude Code session runs its own
|
|
31
|
+
c3-mcp process, so the pid is a faithful stand-in.
|
|
32
|
+
|
|
33
|
+
It is also the right identity for c3_project: that proxy builds a runtime
|
|
34
|
+
for the TARGET project but runs inside the CALLER's process, so the pid
|
|
35
|
+
keeps the edit attributed to the agent that actually asked for it.
|
|
36
|
+
"""
|
|
37
|
+
session = getattr(getattr(svc, "session_mgr", None), "current_session", None) or {}
|
|
38
|
+
return str(session.get("id", "") or "") or f"pid-{os.getpid()}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def allow(svc, denial, *, tool: str, op: str, path) -> str | None:
|
|
42
|
+
"""The `[c3-override:granted]` line when a live grant permits this exact
|
|
43
|
+
call, or None to stay denied.
|
|
44
|
+
|
|
45
|
+
Consumes a use on success — so callers must invoke this only when they
|
|
46
|
+
are about to proceed, never speculatively. Ordering inside `gate_access`
|
|
47
|
+
is policy first, grants second, so a grant is void the instant the policy
|
|
48
|
+
or its layer is switched off (§12.8).
|
|
49
|
+
|
|
50
|
+
Fail-closed by construction: any error resolving the grant store leaves
|
|
51
|
+
the caller on its ordinary refusal path. A grant that cannot be read is
|
|
52
|
+
not a grant.
|
|
53
|
+
"""
|
|
54
|
+
if denial is None:
|
|
55
|
+
return None
|
|
56
|
+
try:
|
|
57
|
+
from services import override_grants as og # noqa: PLC0415 — lazy
|
|
58
|
+
return og.gate_access(svc.project_path, denial, tool=tool, op=op,
|
|
59
|
+
path=str(path), session_id=session_id(svc))
|
|
60
|
+
except Exception:
|
|
61
|
+
return None
|
|
@@ -8,6 +8,7 @@ import sys
|
|
|
8
8
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
+
from cli.tools import _grants
|
|
11
12
|
from cli.tools._helpers import finalize_with_tokens, show_token_ratios
|
|
12
13
|
from core import count_tokens
|
|
13
14
|
from services import access_guard
|
|
@@ -118,7 +119,9 @@ def _compress_single(file_path: str, mode: str, svc, finalize, maybe_facts) -> s
|
|
|
118
119
|
# Access Guard: read verdict up front — covers the map/dense_map paths
|
|
119
120
|
# that never reach compressor.compress_file (docs/access-guard.md §3).
|
|
120
121
|
_verdict = access_guard.verdict(file_path, "read", svc.project_path)
|
|
121
|
-
if _verdict.denial
|
|
122
|
+
if _verdict.denial and not _grants.allow(svc, _verdict.denial,
|
|
123
|
+
tool="c3_compress", op="read",
|
|
124
|
+
path=file_path):
|
|
122
125
|
return finalize("c3_compress", {"file_path": file_path, "mode": mode},
|
|
123
126
|
access_guard.refusal(_verdict.denial, file_path, "read"),
|
|
124
127
|
"access-denied")
|
|
@@ -22,25 +22,22 @@ import threading
|
|
|
22
22
|
from contextlib import contextmanager
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
|
|
25
|
+
from cli.tools import _grants
|
|
25
26
|
from services import access_guard, agent_locks
|
|
26
27
|
from services import credential_store as _cs
|
|
27
28
|
from services.task_store import _FileLock
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
def _session_id(svc) -> str:
|
|
31
|
-
"""This agent's lease identity.
|
|
32
|
+
"""This agent's lease identity — see `cli.tools._grants.session_id`.
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
It is also the right identity for c3_project: that proxy builds a runtime
|
|
39
|
-
for the TARGET project but runs inside the CALLER's process, so the pid
|
|
40
|
-
keeps the edit attributed to the agent that actually asked for it.
|
|
34
|
+
Was defined here and copied into locks.py and override.py, each with a
|
|
35
|
+
comment saying it had to match. P2a makes that invariant load-bearing:
|
|
36
|
+
a grant is minted under the id c3_override computes and consumed under
|
|
37
|
+
the one c3_edit computes, so a divergence would make every approval
|
|
38
|
+
silently fail to apply. One definition, three callers.
|
|
41
39
|
"""
|
|
42
|
-
|
|
43
|
-
return str(session.get("id", "") or "") or f"pid-{os.getpid()}"
|
|
40
|
+
return _grants.session_id(svc)
|
|
44
41
|
|
|
45
42
|
# ── Same-file serialization ───────────────────────────────────────────────
|
|
46
43
|
# In-process locks, keyed by resolved absolute path string.
|
|
@@ -325,7 +322,8 @@ def handle_edit(file_path: str, old_string: str, new_string: str,
|
|
|
325
322
|
# (never replaces) any dedicated vault-file guard.
|
|
326
323
|
op = "write" if path.exists() else "create"
|
|
327
324
|
denial = access_guard.check(str(path), op, svc.project_path)
|
|
328
|
-
if denial
|
|
325
|
+
if denial and not _grants.allow(svc, denial, tool="c3_edit", op=op,
|
|
326
|
+
path=str(path)):
|
|
329
327
|
return finalize("c3_edit", {"file": file_path},
|
|
330
328
|
access_guard.refusal(denial, file_path, op),
|
|
331
329
|
"access-denied")
|
|
@@ -10,6 +10,7 @@ import json
|
|
|
10
10
|
import re
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
|
+
from cli.tools import _grants
|
|
13
14
|
from cli.tools._helpers import finalize_with_tokens, show_token_ratios
|
|
14
15
|
from core import count_tokens
|
|
15
16
|
from services import access_guard
|
|
@@ -31,7 +32,9 @@ def handle_filter(file_path: str, text: str, pattern: str, max_lines: int,
|
|
|
31
32
|
# Access Guard: read verdict (docs/access-guard.md §3), checked before
|
|
32
33
|
# existence so probes can't distinguish missing from denied (R2).
|
|
33
34
|
denial = access_guard.check(file_path, "read", svc.project_path)
|
|
34
|
-
|
|
35
|
+
_granted = _grants.allow(svc, denial, tool="c3_filter", op="read",
|
|
36
|
+
path=file_path) if denial else None
|
|
37
|
+
if denial and not _granted:
|
|
35
38
|
return finalize("c3_filter", {"file": file_path},
|
|
36
39
|
access_guard.refusal(denial, file_path, "read"),
|
|
37
40
|
"access-denied")
|
|
@@ -9,6 +9,7 @@ import subprocess
|
|
|
9
9
|
import sys
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
+
from cli.tools import _grants
|
|
12
13
|
from services import access_guard
|
|
13
14
|
|
|
14
15
|
_SKIP_DIRS = frozenset({
|
|
@@ -115,7 +116,8 @@ def handle_impact(target: str, file_path: str, mode: str, svc, finalize) -> str:
|
|
|
115
116
|
# Access Guard: refuse outright when the named source file is read-denied.
|
|
116
117
|
if file_path:
|
|
117
118
|
denial = access_guard.check(file_path, "read", str(project))
|
|
118
|
-
if denial
|
|
119
|
+
if denial and not _grants.allow(svc, denial, tool="c3_impact",
|
|
120
|
+
op="read", path=file_path):
|
|
119
121
|
return finalize("c3_impact", {"target": target, "mode": mode},
|
|
120
122
|
access_guard.refusal(denial, file_path, "read"),
|
|
121
123
|
"access-denied")
|
|
@@ -12,17 +12,14 @@ and letting go early instead of waiting out the TTL.
|
|
|
12
12
|
force_release is deliberately absent: it is a human override that bumps the
|
|
13
13
|
fencing counter, and it lives in `c3 locks force-release` / the Hub tab.
|
|
14
14
|
"""
|
|
15
|
-
import
|
|
16
|
-
|
|
15
|
+
from cli.tools import _grants
|
|
17
16
|
from services import agent_locks as al
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
def _session_id(svc) -> str:
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
(
|
|
24
|
-
session = getattr(getattr(svc, "session_mgr", None), "current_session", None) or {}
|
|
25
|
-
return str(session.get("id", "") or "") or f"pid-{os.getpid()}"
|
|
20
|
+
"""A lease taken by c3_edit has to be recognised as ours by c3_locks, and
|
|
21
|
+
vice versa — so both read the one definition in `_grants`."""
|
|
22
|
+
return _grants.session_id(svc)
|
|
26
23
|
|
|
27
24
|
|
|
28
25
|
def _split(paths: str) -> list:
|
|
@@ -12,9 +12,9 @@ Layers that can never be escalated (the credential vault, Tier-0 denies, the
|
|
|
12
12
|
dispatcher fail-closed deny, catastrophic shell blocks) are refused at
|
|
13
13
|
creation with `[c3-override:not-escalatable]` and never reach a human.
|
|
14
14
|
"""
|
|
15
|
-
import os
|
|
16
15
|
import time
|
|
17
16
|
|
|
17
|
+
from cli.tools import _grants
|
|
18
18
|
from services import access_guard as ag
|
|
19
19
|
from services import override_policy as opol
|
|
20
20
|
from services import override_requests as orq
|
|
@@ -28,9 +28,8 @@ _DEFAULT_TOOL = {"read": "Read", "write": "Edit"}
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def _session_id(svc) -> str:
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
return str(session.get("id", "") or "") or f"pid-{os.getpid()}"
|
|
31
|
+
"""The identity the grant is bound to — one definition, in `_grants`."""
|
|
32
|
+
return _grants.session_id(svc)
|
|
34
33
|
|
|
35
34
|
|
|
36
35
|
def _fmt(row: dict) -> str:
|
|
@@ -6,6 +6,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
+
from cli.tools import _grants
|
|
9
10
|
from cli.tools._helpers import finalize_with_tokens, maybe_related_facts
|
|
10
11
|
from core import count_tokens
|
|
11
12
|
from services import access_guard
|
|
@@ -123,8 +124,14 @@ def handle_read(file_path: str, symbols: Any = None, lines: Any = None,
|
|
|
123
124
|
# before existence so probes get the same refusal whether or not the
|
|
124
125
|
# target exists (R2). Batch members hit this via the per-file dispatch,
|
|
125
126
|
# so allowed members are served and denied ones carry the S1 line inline.
|
|
127
|
+
# A live grant (P2a) is consulted only on the DENIAL branch. The masked
|
|
128
|
+
# branch below is untouched on purpose: a mask is not a refusal to be
|
|
129
|
+
# lifted, it is a different view being served, and "approve once" has no
|
|
130
|
+
# meaning for it. Widening that is a separate decision, not a side effect.
|
|
126
131
|
_verdict = access_guard.verdict(file_path, "read", svc.project_path)
|
|
127
|
-
if _verdict.denial
|
|
132
|
+
if _verdict.denial and not _grants.allow(svc, _verdict.denial,
|
|
133
|
+
tool="c3_read", op="read",
|
|
134
|
+
path=file_path):
|
|
128
135
|
resp = access_guard.refusal(_verdict.denial, file_path, "read")
|
|
129
136
|
if finalize is None:
|
|
130
137
|
return resp
|
|
@@ -8,6 +8,7 @@ import subprocess
|
|
|
8
8
|
import sys
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
+
from cli.tools import _grants
|
|
11
12
|
from services import access_guard
|
|
12
13
|
|
|
13
14
|
|
|
@@ -169,7 +170,8 @@ async def _validate_one(file_path: str, svc) -> tuple:
|
|
|
169
170
|
# Access Guard: read verdict (docs/access-guard.md §3) — the S1 refusal
|
|
170
171
|
# becomes the batch line for this member; other members are still served.
|
|
171
172
|
denial = access_guard.check(file_path, "read", svc.project_path)
|
|
172
|
-
if denial
|
|
173
|
+
if denial and not _grants.allow(svc, denial, tool="c3_validate",
|
|
174
|
+
op="read", path=file_path):
|
|
173
175
|
return ("SKIP", access_guard.refusal(denial, file_path, "read"))
|
|
174
176
|
full = Path(svc.project_path) / file_path
|
|
175
177
|
if not full.exists():
|
|
@@ -250,7 +252,8 @@ async def _validate_single(file_path: str, svc, finalize) -> str:
|
|
|
250
252
|
"""Original single-file validation path."""
|
|
251
253
|
# Access Guard: read verdict, checked before existence (R2 probe parity).
|
|
252
254
|
denial = access_guard.check(file_path, "read", svc.project_path)
|
|
253
|
-
if denial
|
|
255
|
+
if denial and not _grants.allow(svc, denial, tool="c3_validate",
|
|
256
|
+
op="read", path=file_path):
|
|
254
257
|
return finalize("c3_validate", {"file_path": file_path},
|
|
255
258
|
access_guard.refusal(denial, file_path, "read"),
|
|
256
259
|
"access-denied")
|
{code_context_control-2.71.0 → code_context_control-2.74.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.74.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
|
|
@@ -69,6 +69,7 @@ cli/hub_ui/components/task_board.js
|
|
|
69
69
|
cli/hub_ui/components/toasts.js
|
|
70
70
|
cli/hub_ui/components/topbar.js
|
|
71
71
|
cli/tools/__init__.py
|
|
72
|
+
cli/tools/_grants.py
|
|
72
73
|
cli/tools/_helpers.py
|
|
73
74
|
cli/tools/agent.py
|
|
74
75
|
cli/tools/artifacts.py
|
|
@@ -222,6 +223,7 @@ services/output_filter.py
|
|
|
222
223
|
services/override_grants.py
|
|
223
224
|
services/override_policy.py
|
|
224
225
|
services/override_requests.py
|
|
226
|
+
services/override_wake.py
|
|
225
227
|
services/parser.py
|
|
226
228
|
services/project_manager.py
|
|
227
229
|
services/project_runtime.py
|
|
@@ -352,7 +354,9 @@ tests/test_oracle_subproject_awareness.py
|
|
|
352
354
|
tests/test_oracle_ui_bundle.py
|
|
353
355
|
tests/test_output_filter.py
|
|
354
356
|
tests/test_override_grants.py
|
|
357
|
+
tests/test_override_grants_mcp.py
|
|
355
358
|
tests/test_override_requests.py
|
|
359
|
+
tests/test_override_wake.py
|
|
356
360
|
tests/test_permissions.py
|
|
357
361
|
tests/test_pm_api.py
|
|
358
362
|
tests/test_project_manager.py
|
|
@@ -46,6 +46,7 @@ import hashlib
|
|
|
46
46
|
import json
|
|
47
47
|
import os
|
|
48
48
|
import threading
|
|
49
|
+
import time
|
|
49
50
|
from datetime import datetime, timezone
|
|
50
51
|
from pathlib import Path
|
|
51
52
|
|
|
@@ -58,10 +59,13 @@ from oracle.services.tool_registry import _c3_version
|
|
|
58
59
|
|
|
59
60
|
# 2: /info reports EFFECTIVE capabilities (filtered by the config switches)
|
|
60
61
|
# rather than a static list, so a client can gate its UI instead of probing.
|
|
61
|
-
|
|
62
|
+
# 3: /feed accepts `wait` (long-poll). Advertised as the `feed_wait`
|
|
63
|
+
# capability so a client can choose to hold a connection open instead of
|
|
64
|
+
# polling, rather than discovering support by timing a request.
|
|
65
|
+
API_VERSION = 3
|
|
62
66
|
|
|
63
67
|
CAPABILITIES = [
|
|
64
|
-
"feed", "projects", "health", "pm", "pm_events", "digest",
|
|
68
|
+
"feed", "feed_wait", "projects", "health", "pm", "pm_events", "digest",
|
|
65
69
|
"notifications_ack",
|
|
66
70
|
"credentials", "credentials_write",
|
|
67
71
|
"access", "access_write",
|
|
@@ -90,6 +94,38 @@ _SOURCE_CAP = 1000
|
|
|
90
94
|
_MAX_LIMIT = 200
|
|
91
95
|
_DEFAULT_LIMIT = 50
|
|
92
96
|
|
|
97
|
+
# ── Long-poll (`/feed?wait=`) ─────────────────────────────
|
|
98
|
+
#
|
|
99
|
+
# WHY: the phone's only delivery paths were two pollers — every 15s while the
|
|
100
|
+
# app is open, and Android WorkManager's ~15-MINUTE floor once it is closed.
|
|
101
|
+
# An override request with a 10-minute TTL could therefore expire before the
|
|
102
|
+
# phone was ever told it existed (observed 2026-08-07). `wait` lets a client
|
|
103
|
+
# hold one request open and be answered the moment something lands, turning
|
|
104
|
+
# "up to 15 seconds" into "about a second" without spending a request every
|
|
105
|
+
# 15s to get it.
|
|
106
|
+
#
|
|
107
|
+
# It is NOT push, and nothing here should be read as a claim that it is: a
|
|
108
|
+
# process Android has frozen cannot hold a socket. This narrows the live
|
|
109
|
+
# window. FCM or a foreground service is what would close the closed-app one.
|
|
110
|
+
_MAX_WAIT_S = 30
|
|
111
|
+
# Re-scan cadence while holding. The scan is skipped unless an mtime moved,
|
|
112
|
+
# so this is a stat() per second, not a feed rebuild per second.
|
|
113
|
+
_WAIT_TICK_S = 1.0
|
|
114
|
+
# Each waiter parks a server thread for up to _MAX_WAIT_S, and the Oracle
|
|
115
|
+
# serves the desktop UI and discovery off the same pool. Past the cap `wait`
|
|
116
|
+
# degrades to an ordinary immediate answer rather than queueing behind other
|
|
117
|
+
# waiters. One phone needs one slot; four leaves room for the watch and a
|
|
118
|
+
# spare.
|
|
119
|
+
_MAX_WAITERS = 4
|
|
120
|
+
_waiters = threading.Semaphore(_MAX_WAITERS)
|
|
121
|
+
|
|
122
|
+
#: Files whose mtime means "the feed may have changed" — the same set
|
|
123
|
+
#: ``_last_activity`` reports on, kept literal in both places on purpose. Here
|
|
124
|
+
#: it is a correctness input: a file missing from this tuple is a wake that
|
|
125
|
+
#: never returns early.
|
|
126
|
+
_FEED_FILES = ("activity_log.jsonl", "edit_ledger.jsonl",
|
|
127
|
+
"notifications.jsonl", "pm/pm.json")
|
|
128
|
+
|
|
93
129
|
bp = Blueprint("mobile", __name__, url_prefix="/api/mobile")
|
|
94
130
|
|
|
95
131
|
# Wired by oracle_server at import time (configure) and startup (init_services).
|
|
@@ -489,6 +525,54 @@ def _feed_items_for(path: str, name: str, types: set, severities: set,
|
|
|
489
525
|
return items
|
|
490
526
|
|
|
491
527
|
|
|
528
|
+
def _feed_mtime(targets: list) -> float:
|
|
529
|
+
"""Newest mtime across every target's feed files. 0.0 when none exist."""
|
|
530
|
+
latest = 0.0
|
|
531
|
+
for t in targets:
|
|
532
|
+
c3 = Path(t["path"]) / ".c3"
|
|
533
|
+
for rel in _FEED_FILES:
|
|
534
|
+
try:
|
|
535
|
+
latest = max(latest, (c3 / rel).stat().st_mtime)
|
|
536
|
+
except OSError:
|
|
537
|
+
continue
|
|
538
|
+
return latest
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _hold_for_items(targets: list, collect, wait_s: int):
|
|
542
|
+
"""Block up to *wait_s* for the feed to produce something.
|
|
543
|
+
|
|
544
|
+
Returns ``(items, waited_seconds)``. Rebuilding the feed every tick would
|
|
545
|
+
be a full history read per target, so the loop watches mtimes and only
|
|
546
|
+
pays for a rebuild when one moves. An mtime that moves without producing a
|
|
547
|
+
matching item — a severity this client filters out, say — re-baselines
|
|
548
|
+
instead of re-scanning on every subsequent tick.
|
|
549
|
+
|
|
550
|
+
Returns immediately when every waiter slot is taken. The caller then
|
|
551
|
+
behaves exactly as if ``wait`` had not been passed: a slower client, not a
|
|
552
|
+
wrong one.
|
|
553
|
+
"""
|
|
554
|
+
started = time.monotonic()
|
|
555
|
+
if not _waiters.acquire(blocking=False):
|
|
556
|
+
return [], 0.0
|
|
557
|
+
try:
|
|
558
|
+
seen = _feed_mtime(targets)
|
|
559
|
+
deadline = started + wait_s
|
|
560
|
+
while True:
|
|
561
|
+
remaining = deadline - time.monotonic()
|
|
562
|
+
if remaining <= 0:
|
|
563
|
+
return [], time.monotonic() - started
|
|
564
|
+
time.sleep(min(_WAIT_TICK_S, remaining))
|
|
565
|
+
current = _feed_mtime(targets)
|
|
566
|
+
if current == seen:
|
|
567
|
+
continue
|
|
568
|
+
seen = current
|
|
569
|
+
found = collect()
|
|
570
|
+
if found:
|
|
571
|
+
return found, time.monotonic() - started
|
|
572
|
+
finally:
|
|
573
|
+
_waiters.release()
|
|
574
|
+
|
|
575
|
+
|
|
492
576
|
@bp.route("/feed")
|
|
493
577
|
def mobile_feed():
|
|
494
578
|
"""Merged cross-project feed, newest first.
|
|
@@ -496,11 +580,14 @@ def mobile_feed():
|
|
|
496
580
|
Query: types (csv of notification|activity|edit|session_stat, default
|
|
497
581
|
all), project (optional single project), severity (csv, notifications
|
|
498
582
|
only), since (exclusive watermark — "only newer than"), before
|
|
499
|
-
(exclusive pagination cursor), limit (default 50, max 200)
|
|
583
|
+
(exclusive pagination cursor), limit (default 50, max 200), wait
|
|
584
|
+
(seconds, 0-30 — hold the request open until something matches).
|
|
500
585
|
|
|
501
586
|
Response: {items, next_cursor, truncated}. Cursor is the ts of the last
|
|
502
587
|
returned item; identical-ts items can repeat across pages, so clients
|
|
503
|
-
dedup by item id.
|
|
588
|
+
dedup by item id. A `wait` request also carries `waited_s`, and an empty
|
|
589
|
+
`items` after a full hold is the normal, expected answer — the client
|
|
590
|
+
reconnects with the same watermark.
|
|
504
591
|
"""
|
|
505
592
|
if _scanner is None:
|
|
506
593
|
return jsonify({"error": "not initialized"}), 500
|
|
@@ -523,6 +610,18 @@ def mobile_feed():
|
|
|
523
610
|
except (TypeError, ValueError):
|
|
524
611
|
limit = _DEFAULT_LIMIT
|
|
525
612
|
|
|
613
|
+
try:
|
|
614
|
+
wait_s = max(0, min(_MAX_WAIT_S, int(request.args.get("wait", 0))))
|
|
615
|
+
except (TypeError, ValueError):
|
|
616
|
+
wait_s = 0
|
|
617
|
+
# Holding open only makes sense against a watermark. Without `since` the
|
|
618
|
+
# first scan matches history and returns instantly, so a client that
|
|
619
|
+
# forgot it would silently get the old behaviour and never learn why its
|
|
620
|
+
# push felt slow; with `before` this is a pagination cursor, where waiting
|
|
621
|
+
# is meaningless.
|
|
622
|
+
if before or not since:
|
|
623
|
+
wait_s = 0
|
|
624
|
+
|
|
526
625
|
project_arg = (request.args.get("project") or "").strip()
|
|
527
626
|
if project_arg:
|
|
528
627
|
resolved, err = _project_or_404(project_arg)
|
|
@@ -536,10 +635,17 @@ def mobile_feed():
|
|
|
536
635
|
if p.get("has_c3") and p.get("path")
|
|
537
636
|
and (Path(p["path"]) / ".c3").is_dir()]
|
|
538
637
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
638
|
+
def collect() -> list:
|
|
639
|
+
out: list[dict] = []
|
|
640
|
+
for t in targets:
|
|
641
|
+
out.extend(_feed_items_for(t["path"], t["name"], types,
|
|
642
|
+
severities, since, before))
|
|
643
|
+
return out
|
|
644
|
+
|
|
645
|
+
items = collect()
|
|
646
|
+
waited = 0.0
|
|
647
|
+
if not items and wait_s:
|
|
648
|
+
items, waited = _hold_for_items(targets, collect, wait_s)
|
|
543
649
|
|
|
544
650
|
items.sort(key=lambda i: i["ts"], reverse=True)
|
|
545
651
|
truncated = len(items) > limit
|
|
@@ -548,6 +654,9 @@ def mobile_feed():
|
|
|
548
654
|
"items": page,
|
|
549
655
|
"next_cursor": page[-1]["ts"] if truncated and page else None,
|
|
550
656
|
"truncated": truncated,
|
|
657
|
+
# Only when the client asked to hold, so the response shape is
|
|
658
|
+
# byte-identical for every client that did not.
|
|
659
|
+
**({"waited_s": round(waited, 2)} if wait_s else {}),
|
|
551
660
|
})
|
|
552
661
|
|
|
553
662
|
|
|
@@ -1634,6 +1743,10 @@ def mobile_enforcement_set():
|
|
|
1634
1743
|
# (e.g. `path_key`) cannot silently start crossing the network.
|
|
1635
1744
|
|
|
1636
1745
|
#: §3.3 field order, verbatim. The mobile client reads these names.
|
|
1746
|
+
#: The one `override` key no remote surface may write. See the refusal in
|
|
1747
|
+
#: `mobile_overrides_policy_set` for why it is a 403 rather than a 400.
|
|
1748
|
+
_WAKE_KEY = "wake"
|
|
1749
|
+
|
|
1637
1750
|
_OVERRIDE_FIELDS = (
|
|
1638
1751
|
"id", "project_path", "session_id", "created_at", "expires_at", "status",
|
|
1639
1752
|
"layer", "rule", "rule_class", "scope", "tool", "op", "path", "refusal",
|
|
@@ -1809,6 +1922,17 @@ def mobile_overrides_policy_set():
|
|
|
1809
1922
|
section = data.get("override")
|
|
1810
1923
|
if not isinstance(section, dict):
|
|
1811
1924
|
return jsonify({"error": "body needs an 'override' object"}), 400
|
|
1925
|
+
if _WAKE_KEY in section:
|
|
1926
|
+
# `wake` is an argv this machine will execute. Everything else on this
|
|
1927
|
+
# route widens what a tap can approve; this one would decide what runs
|
|
1928
|
+
# when it does. A bearer token from a phone is authentication, not
|
|
1929
|
+
# physical presence, so it stays a desktop edit to a human-owned file.
|
|
1930
|
+
return jsonify({
|
|
1931
|
+
"error": "'wake' cannot be set from the phone — it names a "
|
|
1932
|
+
"command this machine runs. Edit .c3/config.json on the "
|
|
1933
|
+
"desktop.",
|
|
1934
|
+
"key": _WAKE_KEY,
|
|
1935
|
+
}), 403
|
|
1812
1936
|
unknown = sorted(set(section) - set(opol.DEFAULTS))
|
|
1813
1937
|
if unknown:
|
|
1814
1938
|
# §3.1: unknown keys are a hard error, never a silent no-op.
|
|
@@ -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.74.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"
|
|
@@ -709,11 +709,12 @@ def _override_offer(denial: Denial, path, operation: str, tool: str) -> str:
|
|
|
709
709
|
that can never be escalated: an agent must not learn from a refusal that
|
|
710
710
|
a request surface exists for the credential vault (§6).
|
|
711
711
|
|
|
712
|
-
Restricted to the hook surface on purpose
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
712
|
+
Restricted to the hook surface on purpose — see ``refusal``. The rule was
|
|
713
|
+
written when only the PreToolUse gates consulted grants; P2a taught the
|
|
714
|
+
``c3_*`` content surfaces to consult them too, so extending the offer to
|
|
715
|
+
the MCP surface is now defensible. It is deliberately NOT done here: that
|
|
716
|
+
is a separate, wider change (every c3_* refusal grows a line) and belongs
|
|
717
|
+
in its own review, not smuggled in beside a fix for the branch ordering.
|
|
717
718
|
"""
|
|
718
719
|
try:
|
|
719
720
|
from services import override_policy as _op # noqa: PLC0415 — cycle
|
|
@@ -738,7 +739,26 @@ def refusal(denial: Denial, path, operation: str, *, surface: str = "mcp",
|
|
|
738
739
|
the agent it may ASK (docs/override-requests.md §6). The pinned strings
|
|
739
740
|
above are unchanged: the append is absent unless the project opted in,
|
|
740
741
|
which is off by default.
|
|
742
|
+
|
|
743
|
+
The append happens HERE, once, for every kind — not inside a branch. It
|
|
744
|
+
used to live at the tail of the deny branch, which sits below the mask and
|
|
745
|
+
read-only early returns, so those two kinds could never carry an offer on
|
|
746
|
+
any surface even though `rule_class_for_denial` marks both escalatable.
|
|
747
|
+
`access_readonly` was the only layer anyone had turned on, so in practice
|
|
748
|
+
the invitation half of Override Requests had never fired once.
|
|
741
749
|
"""
|
|
750
|
+
body = _refusal_body(denial, path, operation, surface=surface,
|
|
751
|
+
tool=tool, project=project)
|
|
752
|
+
# Hook only, still: the offer promises that a 'yes' makes the retry work,
|
|
753
|
+
# and the proxy surface answers for a DIFFERENT project's policy.
|
|
754
|
+
if surface == "hook":
|
|
755
|
+
return body + _override_offer(denial, path, operation, tool)
|
|
756
|
+
return body
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
def _refusal_body(denial: Denial, path, operation: str, *, surface: str,
|
|
760
|
+
tool: str, project: str) -> str:
|
|
761
|
+
"""The pinned string itself, with no override append."""
|
|
742
762
|
p, glob, scope = _cap(path), denial.rule, denial.scope
|
|
743
763
|
if denial.kind == _KIND_MASK:
|
|
744
764
|
if operation in ("write", "create", "delete"):
|
|
@@ -762,12 +782,20 @@ def refusal(denial: Denial, path, operation: str, *, surface: str = "mcp",
|
|
|
762
782
|
"`c3 access list` or the Access tab."
|
|
763
783
|
)
|
|
764
784
|
if denial.kind == _KIND_READ_ONLY:
|
|
785
|
+
# `{operation}`, not a hardcoded "write". A read-only rule blocks the
|
|
786
|
+
# whole write class, and for a file that does not exist yet the tools
|
|
787
|
+
# call that operation `create`. Saying "write" told the agent to ask
|
|
788
|
+
# the user to approve `op='write'`, the approval was minted for
|
|
789
|
+
# `write`, and the grant matcher — which compares op exactly — then
|
|
790
|
+
# refused the `create` the tool actually attempted. A real approval
|
|
791
|
+
# was spent and silently discarded (§4 near_miss).
|
|
765
792
|
return (
|
|
766
|
-
f"{TAG_READ_ONLY}
|
|
793
|
+
f"{TAG_READ_ONLY} {operation} denied for {p} by Access Guard rule "
|
|
767
794
|
f"'{glob}' ({scope} scope). The effective policy is read-only; "
|
|
768
|
-
"reads are evaluated separately. Do not retry the
|
|
769
|
-
"the affected step blocked and continue with unaffected
|
|
770
|
-
"report the skip. Rules: `c3 access list` or the Access
|
|
795
|
+
f"reads are evaluated separately. Do not retry the {operation}. "
|
|
796
|
+
"Mark the affected step blocked and continue with unaffected "
|
|
797
|
+
"files; report the skip. Rules: `c3 access list` or the Access "
|
|
798
|
+
"tab."
|
|
771
799
|
)
|
|
772
800
|
if surface == "hook":
|
|
773
801
|
return (
|
|
@@ -776,7 +804,7 @@ def refusal(denial: Denial, path, operation: str, *, surface: str = "mcp",
|
|
|
776
804
|
"decision, not a transient error — do not retry through another "
|
|
777
805
|
"tool or the shell. Mark the affected step blocked and continue "
|
|
778
806
|
"with unaffected files. Rules: `c3 access list`."
|
|
779
|
-
)
|
|
807
|
+
)
|
|
780
808
|
if surface == "proxy":
|
|
781
809
|
return (
|
|
782
810
|
f"{TAG_DENIED} {operation} denied for {p} through project "
|