code-context-control 2.101.0__tar.gz → 2.102.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.101.0/code_context_control.egg-info → code_context_control-2.102.0}/PKG-INFO +1 -1
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/c3.py +51 -11
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_access_guard.py +106 -9
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_dispatch.py +91 -1
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_pretool_enforce.py +24 -5
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/mcp_server.py +1 -1
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/_grants.py +26 -9
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/artifacts.py +33 -2
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/override.py +15 -5
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/shell.py +82 -1
- {code_context_control-2.101.0 → code_context_control-2.102.0/code_context_control.egg-info}/PKG-INFO +1 -1
- {code_context_control-2.101.0 → code_context_control-2.102.0}/code_context_control.egg-info/SOURCES.txt +1 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/pyproject.toml +1 -1
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/access_guard.py +41 -5
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/artifact_store.py +36 -9
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/claude_md.py +22 -2
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/override_grants.py +20 -9
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/session_manager.py +18 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_artifact_tool.py +41 -4
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_confirm_guard.py +46 -6
- code_context_control-2.102.0/tests/test_confirm_guard_wiring.py +482 -0
- code_context_control-2.102.0/tests/test_ide_workflow_adaptation.py +235 -0
- code_context_control-2.101.0/tests/test_ide_workflow_adaptation.py +0 -121
- {code_context_control-2.101.0 → code_context_control-2.102.0}/LICENSE +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/README.md +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/__init__.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/_hook_utils.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/_shell_writes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/commands/__init__.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/commands/common.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/commands/parser.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/docs.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/edits.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/guide/access.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/guide/bitbucket.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/guide/credentials.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/guide/getting-started.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/guide/index.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/guide/masking.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/guide/oracle.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/guide/shared.css +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/guide/tools.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/guide/workflow.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_artifact.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_auto_snapshot.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_c3_signal.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_c3read.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_edit_ledger.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_edit_unlock.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_filter.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_ghost_files.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_prompt_recall.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_read.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_session_stats.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hook_terse_advisor.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_server.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/app.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/add_project.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/config_editor.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/drill_health.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/drill_panel.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/drill_subprojects.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/drill_tasks.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/drill_views.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/global_search.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/hub_access.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/hub_ci.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/hub_cred_audit.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/hub_credentials.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/hub_enforcement.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/hub_locks.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/hub_tokens.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/mcp_manager.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/modals.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/project_card.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/project_tree.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/session_drawer.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/settings_modal.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/sidebar.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/summary_bar.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/task_board.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/toasts.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/components/topbar.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui/state.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/hub_ui.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/mcp_proxy.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/progress.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/server.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/__init__.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/_helpers.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/agent.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/bitbucket.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/ci.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/compress.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/credentials.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/delegate.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/edit.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/edit_verify.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/edits.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/federate.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/filter.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/impact.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/jira.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/locks.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/memory.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/project.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/read.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/search.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/session.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/status.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/tasks.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/tools/validate.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/api.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/app.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/access.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/bitbucket.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/chat.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/credentials.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/dashboard.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/edits.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/enforcement.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/instructions.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/jira.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/memory.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/sessions.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/settings.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/sidebar.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/components/tasks.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/icons.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/pm_shared.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/shared.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui/theme.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui_legacy.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/cli/ui_nano.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/code_context_control.egg-info/dependency_links.txt +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/code_context_control.egg-info/entry_points.txt +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/code_context_control.egg-info/requires.txt +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/code_context_control.egg-info/top_level.txt +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/core/__init__.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/core/config.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/core/ide.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/core/mcp_toml.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/core/web_security.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/__init__.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/config.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/mcp_oracle.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/oracle_server.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/oracle_ui.html +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/__init__.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/activity_reporter.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/api_auth.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/c3_bridge.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/chat_engine.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/chat_poll.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/chat_store.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/cross_memory.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/discovery_audit.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/federated_graph.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/health_checker.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/insight_engine.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/local_session.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/memory_reader.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/memory_writer.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/mobile_api.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/project_scanner.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/review_agent.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/tool_executor.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/services/tool_registry.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/activity.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/agents.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/app.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/busy.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/chat/conversations.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/chat/input.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/chat/markdown.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/chat/send.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/chat/stream_renderer.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/chat/toolbar.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/core.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/crossgraph.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/header.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/insights.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/projects.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/qrcode.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/settings.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/suggestions.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/oracle/ui/theme_tabs.js +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/__init__.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/access_telemetry.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/activity_log.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/agent_base.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/agent_locks.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/agents.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/artifact_defs.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/auto_memory.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/background_service.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/bench/__init__.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/bench/external/__init__.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/bench/external/aider_polyglot.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/bench/external/swe_bench.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/benchmark_dashboard.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/bitbucket_client.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/bitbucket_credentials.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/ci_act.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/ci_cache.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/ci_expr.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/ci_failures.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/ci_github.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/ci_impact.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/ci_intel.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/ci_runner.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/ci_workflow.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/circuit_breaker.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/compressor.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/context_snapshot.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/conversation_store.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/cred_audit.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/cred_telemetry.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/credential_store.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/doc_index.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/e2e_benchmark.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/e2e_evaluator.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/e2e_tasks.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/edit_ledger.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/embedding_index.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/enforcement_policy.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/error_reporting.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/file_memory.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/git_context.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/hub_service.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/indexer.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/jira_client.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/jira_cloud.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/jira_credentials.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/jira_data_center.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/jira_links.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/mask_activation.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/mask_mirror.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/mask_presets.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/memory.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/memory_consolidator.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/memory_distiller.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/memory_graph.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/memory_grounder.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/memory_queue.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/memory_scorer.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/metrics.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/notifications.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/ollama_bridge.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/ollama_client.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/ollama_credentials.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/oracle_service.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/output_filter.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/override_policy.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/override_requests.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/override_wake.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/parser.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/project_manager.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/project_runtime.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/protocol.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/proxy_state.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/repo_map.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/repo_shape.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/retention.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/retrieval_broker.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/router.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/runtime.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/scanner.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/session_benchmark.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/session_preloader.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/subprojects.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/task_store.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/telemetry.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/text_index.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/time_tracker.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/tool_classifier.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/transcript_index.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/validation_cache.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/vector_store.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/watcher.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/services/win_subprocess.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/setup.cfg +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_access_builtin_optout.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_access_guard.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_access_guard_meta.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_access_guard_shell_project.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_access_guard_surfaces.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_access_guard_wiring.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_activity_reporter.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_agent_locks.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_aider_polyglot.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_antigravity_transition.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_artifact_store.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_background_service.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_bitbucket_cli_smoke.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_bitbucket_client.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_bitbucket_config_fallback.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_bitbucket_credentials.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_bitbucket_tool.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_builtin_modes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_c3_shell.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_chat_poll.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_ci_act.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_ci_cache.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_ci_expr.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_ci_impact.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_ci_intel.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_ci_runner.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_ci_workflow.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_circuit_breaker.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_claude_md_merge.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_cli_smoke.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_compressor_large_file.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_cooccurrence_bounds.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_cred_audit.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_cred_telemetry.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_credential_login_kind.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_credential_store.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_credential_ui_parity.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_credentials_routes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_credentials_tool.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_creds_cli_smoke.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_delegate_cascade.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_delegate_version_probe.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_e2e_benchmark.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_edit_ledger_deadline.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_edit_ledger_hook.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_edit_locking.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_edit_normalization.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_edit_verify.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_embedding_index_deadlock.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_enforcement_flip.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_enforcement_policy.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_enforcement_routes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_env_parser.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_failed_calls_do_not_unlock.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_federated_graph.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_filter_backoff.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_ghost_files.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_ghost_generation.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_git_branch_awareness.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hook_access_guard.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hook_codex_compat.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hook_dispatch.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hook_pretool_enforce.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hook_prompt_recall.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hook_smoke.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hook_state.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hook_utf8_stdio.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hub_credentials_route.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hub_credentials_routes_write.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hub_enforcement_routes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hub_inspect_api.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hub_locks_routes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hub_oracle_service_routes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hub_override_routes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hub_server_smoke.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_hub_subproject_links.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_init_clear_preserves_user_content.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_init_scan.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_install_mcp_entrypoint.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_jira_cli_smoke.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_jira_client.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_jira_config_fallback.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_jira_credentials.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_jira_links.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_jira_routes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_jira_tool.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_lazy_store_init.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_mask_guard.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_mask_routes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_mask_surfaces.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_mcp_host_guard.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_mcp_memory_timeout.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_mcp_server_smoke.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_mcp_toml.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_memory_distiller.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_memory_graph_api.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_memory_queue.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_memory_system.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_mobile_api.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_mobile_extras.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_mobile_override_routes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_mobile_security_api.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_no_stale_model_pins.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_notification_dedup.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_notification_discipline.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_ollama_credentials.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_api_auth.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_apikey_api.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_c3_bridge.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_chat_engine.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_chat_store.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_discovery_api.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_discovery_audit.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_health_probe.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_local_auth.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_ollama_bridge.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_scanner_cache.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_security_fixes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_session_bootstrap.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_subproject_awareness.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_oracle_ui_bundle.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_output_filter.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_override_grants.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_override_grants_mcp.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_override_requests.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_override_wake.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_permissions.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_pm_api.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_project_manager.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_project_manager_merge.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_project_tool.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_read_coercion.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_read_edit_parity.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_related_facts.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_repo_map.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_repo_shape.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_response_boilerplate.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_retention.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_review_digest.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_service_durability.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_session_benchmark.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_session_budget.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_shell_creds.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_shell_robustness.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_shell_write_ledger.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_shell_writes.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_subproject_clear_scope.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_subproject_depth_consumers.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_subproject_exclusion.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_subproject_federation.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_subproject_linkage_api.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_subprojects.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_swe_bench.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_task_store.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_task_tool.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_time_tracker.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_token_telemetry.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_token_tracking.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_tool_registry.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_ui_jsx_syntax.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_upgrade_and_version.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_validate.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_vault_write_guard.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_version_sync.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_web_security.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tests/test_windows_reliability.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/__init__.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/backend.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/main.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/__init__.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/benchmark_view.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/claudemd_view.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/compress_view.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/index_view.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/init_view.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/mcp_view.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/optimize_view.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/pipe_view.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/projects_view.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/search_view.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/session_view.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/stats.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/screens/ui_view.py +0 -0
- {code_context_control-2.101.0 → code_context_control-2.102.0}/tui/theme.tcss +0 -0
{code_context_control-2.101.0/code_context_control.egg-info → code_context_control-2.102.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.102.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
|
|
@@ -92,7 +92,7 @@ console = Console() if HAS_RICH else None
|
|
|
92
92
|
# Config
|
|
93
93
|
CONFIG_DIR = ".c3"
|
|
94
94
|
CONFIG_FILE = ".c3/config.json"
|
|
95
|
-
__version__ = "2.
|
|
95
|
+
__version__ = "2.102.0"
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def _compress_file_cli(compressor, path, mode="smart", **kw):
|
|
@@ -4212,6 +4212,7 @@ def cmd_pipe(args):
|
|
|
4212
4212
|
|
|
4213
4213
|
|
|
4214
4214
|
from services.claude_md import C3_COMPACT_WORKFLOW as _SHARED_C3_COMPACT_WORKFLOW
|
|
4215
|
+
from services.claude_md import VSCODE_INSTRUCTIONS_FILE
|
|
4215
4216
|
from services.claude_md import VSCODE_SESSION_INIT as _VSCODE_SESSION_INIT
|
|
4216
4217
|
from services.claude_md import adapt_workflow_for_ide as _adapt_workflow_for_ide
|
|
4217
4218
|
|
|
@@ -4433,14 +4434,27 @@ def _toml_section_bool_value(toml_path: Path, section: str, key: str) -> bool |
|
|
|
4433
4434
|
return None
|
|
4434
4435
|
|
|
4435
4436
|
|
|
4436
|
-
def _ensure_instruction_workflow(instructions_path: Path, template: str,
|
|
4437
|
-
|
|
4437
|
+
def _ensure_instruction_workflow(instructions_path: Path, template: str,
|
|
4438
|
+
required_markers: list[str],
|
|
4439
|
+
project_path: str = "") -> str:
|
|
4440
|
+
"""Ensure an instructions file carries the CURRENT C3 workflow.
|
|
4438
4441
|
|
|
4439
4442
|
Returns one of: "written", "updated", "kept".
|
|
4443
|
+
|
|
4444
|
+
Marker PRESENCE used to mean "up to date", which is why this function
|
|
4445
|
+
could report `kept` on a doc generated four releases ago: the markers are
|
|
4446
|
+
stable words like "SEARCH FIRST" and "c3_edit", so they survive every
|
|
4447
|
+
template change. A doc carrying the managed sentinels is now refreshed
|
|
4448
|
+
through ``merge_c3_block`` — the same non-destructive regeneration
|
|
4449
|
+
CLAUDE.md and AGENTS.md get — and `kept` means the block already matches
|
|
4450
|
+
byte for byte (v2.102.0). A marker-less legacy doc keeps the old
|
|
4451
|
+
prepend-and-preserve path, which ``write_c3_instruction_doc`` handles.
|
|
4440
4452
|
"""
|
|
4453
|
+
from services.claude_md import C3_BLOCK_BEGIN, merge_c3_block, wrap_c3_block, write_c3_instruction_doc
|
|
4454
|
+
|
|
4441
4455
|
if not instructions_path.exists():
|
|
4442
|
-
instructions_path
|
|
4443
|
-
|
|
4456
|
+
write_c3_instruction_doc(instructions_path, template,
|
|
4457
|
+
project_path=project_path or None)
|
|
4444
4458
|
return "written"
|
|
4445
4459
|
|
|
4446
4460
|
try:
|
|
@@ -4448,6 +4462,14 @@ def _ensure_instruction_workflow(instructions_path: Path, template: str, require
|
|
|
4448
4462
|
except Exception:
|
|
4449
4463
|
existing = ""
|
|
4450
4464
|
|
|
4465
|
+
# Managed doc → regenerate the block in place, preserving user content.
|
|
4466
|
+
if C3_BLOCK_BEGIN in existing:
|
|
4467
|
+
if merge_c3_block(existing, wrap_c3_block(template)) == existing:
|
|
4468
|
+
return "kept"
|
|
4469
|
+
write_c3_instruction_doc(instructions_path, template,
|
|
4470
|
+
project_path=project_path or None)
|
|
4471
|
+
return "updated"
|
|
4472
|
+
|
|
4451
4473
|
if all(marker in existing for marker in required_markers):
|
|
4452
4474
|
return "kept"
|
|
4453
4475
|
|
|
@@ -4460,7 +4482,8 @@ def _ensure_instruction_workflow(instructions_path: Path, template: str, require
|
|
|
4460
4482
|
return "updated"
|
|
4461
4483
|
|
|
4462
4484
|
|
|
4463
|
-
def _ensure_codex_agents_workflow(agents_md_path: Path
|
|
4485
|
+
def _ensure_codex_agents_workflow(agents_md_path: Path,
|
|
4486
|
+
project_path: str = "") -> str:
|
|
4464
4487
|
"""Ensure AGENTS.md contains the mandatory C3 workflow for Codex sessions."""
|
|
4465
4488
|
required_markers = [
|
|
4466
4489
|
"C3 Tools",
|
|
@@ -4474,10 +4497,12 @@ def _ensure_codex_agents_workflow(agents_md_path: Path) -> str:
|
|
|
4474
4497
|
"c3_memory",
|
|
4475
4498
|
"c3_filter",
|
|
4476
4499
|
]
|
|
4477
|
-
return _ensure_instruction_workflow(agents_md_path, _AGENTS_MD_CONTENT,
|
|
4500
|
+
return _ensure_instruction_workflow(agents_md_path, _AGENTS_MD_CONTENT,
|
|
4501
|
+
required_markers, project_path)
|
|
4478
4502
|
|
|
4479
4503
|
|
|
4480
|
-
def _ensure_vscode_instructions_workflow(instructions_path: Path
|
|
4504
|
+
def _ensure_vscode_instructions_workflow(instructions_path: Path,
|
|
4505
|
+
project_path: str = "") -> str:
|
|
4481
4506
|
"""Ensure VS Code Copilot instructions include the latest C3 workflow markers."""
|
|
4482
4507
|
required_markers = [
|
|
4483
4508
|
"C3 Tools",
|
|
@@ -4491,7 +4516,9 @@ def _ensure_vscode_instructions_workflow(instructions_path: Path) -> str:
|
|
|
4491
4516
|
"c3_memory",
|
|
4492
4517
|
"c3_filter",
|
|
4493
4518
|
]
|
|
4494
|
-
return _ensure_instruction_workflow(instructions_path,
|
|
4519
|
+
return _ensure_instruction_workflow(instructions_path,
|
|
4520
|
+
_COPILOT_INSTRUCTIONS_CONTENT,
|
|
4521
|
+
required_markers, project_path)
|
|
4495
4522
|
|
|
4496
4523
|
|
|
4497
4524
|
def _safe_read_json(path: Path, label: str = "") -> dict:
|
|
@@ -4957,6 +4984,7 @@ back to native tools as the task progresses.
|
|
|
4957
4984
|
- **Bitbucket** (v2.30.0+, when `c3 bitbucket login` has run): `c3_bitbucket(action='list_prs|get_pr|merge_pr|...')` — self-hosted Bitbucket Data Center / Server. Token in OS keyring; mutating actions auto-log to the edit ledger.
|
|
4958
4985
|
- **Cross-project** (v2.31.0+): `c3_project(action='list|scan|search|read|edit|...', project='<name|path>')` — discover and operate on OTHER c3-installed projects. Reads run freely; writes (edit/shell/memory) need `allow_write=true`.
|
|
4959
4986
|
- **Masked paths** (v2.63.0+): content prefixed `[c3-mask:transformed]` is a policy-transformed VIEW, not the file. Values may be synthetic, rows withheld, bodies stripped — don't copy literals out of it or infer completeness from it. Masked paths are read-only and refuse shell/git/validate/impact/filter/delegate (`[c3-mask:unsupported]`). That's policy, not a transient error — report the block instead of routing around it.
|
|
4987
|
+
- **Confirm holds** (v2.97.0+): `[c3-access:confirm]` is a PAUSE, not a block — a human must approve this exact write. Since v2.100.0 the agent-config files (`.mcp.json`, instruction docs, `.claude` hook/skill/agent/command bodies) hold by default in EVERY C3 project, reads stay open. Read the S8 tail: it names the filed request (`c3_override(action='wait', request_id='...', timeout_s=180)`, then retry the same call once if approved), or names a surface that files one, or gives a reason to obey. Never retry before a decision and never route around a hold via the shell.
|
|
4960
4988
|
|
|
4961
4989
|
## Self-Check
|
|
4962
4990
|
If you haven't called a c3_* tool in several turns during active development, re-engage
|
|
@@ -5034,10 +5062,20 @@ def _instruction_documents_for_project() -> list[tuple[str, str]]:
|
|
|
5034
5062
|
|
|
5035
5063
|
Used by uninstall/cleanup paths, so it must keep listing legacy docs
|
|
5036
5064
|
(GEMINI.md — profile removed in v2.52; empty template, never generated).
|
|
5065
|
+
|
|
5066
|
+
``.github/copilot-instructions.md`` was absent from this list until
|
|
5067
|
+
v2.102.0, and that absence was the whole bug: the managed-block sync
|
|
5068
|
+
that regenerates CLAUDE.md and AGENTS.md on every install-mcp never
|
|
5069
|
+
touched it, while ``_ensure_vscode_instructions_workflow`` reported
|
|
5070
|
+
`kept` on marker presence alone. In a vscode-primary project the third
|
|
5071
|
+
git-tracked instruction doc silently drifted a release behind on every
|
|
5072
|
+
template change — v2.100.0's agent-config confirm tier reached the
|
|
5073
|
+
Claude Code and Codex docs and never reached Copilot's.
|
|
5037
5074
|
"""
|
|
5038
5075
|
return [
|
|
5039
5076
|
("CLAUDE.md", _CLAUDE_MD_CONTENT),
|
|
5040
5077
|
("AGENTS.md", _AGENTS_MD_CONTENT),
|
|
5078
|
+
(VSCODE_INSTRUCTIONS_FILE, _COPILOT_INSTRUCTIONS_CONTENT),
|
|
5041
5079
|
("GEMINI.md", ""),
|
|
5042
5080
|
]
|
|
5043
5081
|
|
|
@@ -5692,7 +5730,8 @@ def cmd_install_mcp(args):
|
|
|
5692
5730
|
if profile.name == "vscode":
|
|
5693
5731
|
# 1. Ensure copilot-instructions.md has the latest C3 workflow markers
|
|
5694
5732
|
instructions_path = target / ".github" / "copilot-instructions.md"
|
|
5695
|
-
vs_state = _ensure_vscode_instructions_workflow(instructions_path
|
|
5733
|
+
vs_state = _ensure_vscode_instructions_workflow(instructions_path,
|
|
5734
|
+
str(target))
|
|
5696
5735
|
if vs_state == "written":
|
|
5697
5736
|
print(f"Wrote {instructions_path}")
|
|
5698
5737
|
elif vs_state == "updated":
|
|
@@ -5731,7 +5770,8 @@ def cmd_install_mcp(args):
|
|
|
5731
5770
|
# ── Codex AGENTS.md enforcement file ──────────────────────
|
|
5732
5771
|
if profile.name == "codex":
|
|
5733
5772
|
agents_md_path = target / "AGENTS.md"
|
|
5734
|
-
agents_state = _ensure_codex_agents_workflow(agents_md_path
|
|
5773
|
+
agents_state = _ensure_codex_agents_workflow(agents_md_path,
|
|
5774
|
+
str(target))
|
|
5735
5775
|
if agents_state == "written":
|
|
5736
5776
|
print(f"Wrote {agents_md_path}")
|
|
5737
5777
|
elif agents_state == "updated":
|
|
@@ -6,9 +6,11 @@ dispatcher's deny-beats-allow merge makes a deny here final. hook_dispatch
|
|
|
6
6
|
treats this module as _FAIL_CLOSED: if it fails to import or raises, write-
|
|
7
7
|
class native tools are denied instead of sailing through.
|
|
8
8
|
|
|
9
|
-
Bash coverage is a best-effort
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Bash coverage is a best-effort token scan — advisory by design
|
|
10
|
+
(docs/access-guard.md §3/§6); it catches an agent naming a denied path, not
|
|
11
|
+
an adversary hiding one. Two passes: every path-shaped token as a READ
|
|
12
|
+
(existence-gated), then the command's WRITE targets as writes (v2.102.0), so
|
|
13
|
+
a redirect into a confirm-held or read-only file holds like any other write.
|
|
12
14
|
"""
|
|
13
15
|
import os
|
|
14
16
|
import re
|
|
@@ -29,6 +31,16 @@ try:
|
|
|
29
31
|
except Exception: # pragma: no cover — telemetry is never load-bearing
|
|
30
32
|
access_telemetry = None
|
|
31
33
|
|
|
34
|
+
# The same write-target extractor the edit ledger uses after the fact. Its
|
|
35
|
+
# absence degrades the shell branch to the read-only scan, never to a crash.
|
|
36
|
+
try:
|
|
37
|
+
from _shell_writes import shell_write_targets
|
|
38
|
+
except Exception: # pragma: no cover — package-style import (dispatcher, tests)
|
|
39
|
+
try:
|
|
40
|
+
from cli._shell_writes import shell_write_targets
|
|
41
|
+
except Exception:
|
|
42
|
+
shell_write_targets = None
|
|
43
|
+
|
|
32
44
|
_WRITE_TOOLS = {"Edit", "Write", "MultiEdit", "NotebookEdit"}
|
|
33
45
|
_READ_TOOLS = {"Read", "SearchText"}
|
|
34
46
|
_SEARCH_TOOLS = {"Grep", "Glob", "FindFiles"}
|
|
@@ -206,21 +218,41 @@ def _record(denial, tool: str, operation: str, path: str,
|
|
|
206
218
|
|
|
207
219
|
|
|
208
220
|
def _override_allows(base: str, denial, *, tool: str, op: str, path: str,
|
|
209
|
-
session_id: str):
|
|
221
|
+
session_id: str, peek: bool = False):
|
|
210
222
|
"""A live override grant covering this exact call, or None (spec §5).
|
|
211
223
|
|
|
212
224
|
Lazy import and policy-before-grants ordering keep the hot path free: a
|
|
213
225
|
project that never turned overrides on pays one dict lookup and never
|
|
214
226
|
opens the grants file. Any failure returns None — the denial stands.
|
|
227
|
+
|
|
228
|
+
``peek`` looks without consuming; see ``_granted`` for why.
|
|
215
229
|
"""
|
|
216
230
|
try:
|
|
217
231
|
from services import override_grants as og # noqa: PLC0415 — lazy
|
|
218
232
|
return og.gate_access(base, denial, tool=tool, op=op, path=path,
|
|
219
|
-
session_id=session_id)
|
|
233
|
+
session_id=session_id, peek=peek)
|
|
220
234
|
except Exception:
|
|
221
235
|
return None # fail closed: no grant, ordinary denial
|
|
222
236
|
|
|
223
237
|
|
|
238
|
+
def _granted(line: str, defer: bool, consume) -> dict:
|
|
239
|
+
"""The allow-by-grant output.
|
|
240
|
+
|
|
241
|
+
Under the dispatcher (``defer`` True) the use is NOT burned here: the
|
|
242
|
+
provisional line goes out with an ``_on_allow`` callable, and
|
|
243
|
+
hook_dispatch runs it only once every PreToolUse sub-hook has voted
|
|
244
|
+
allow. Before v2.102.0 this hook consumed first and a strict-mode
|
|
245
|
+
discipline deny from hook_pretool_enforce then won the merge — grant
|
|
246
|
+
spent, nothing written, and the user asked twice for one edit. Run
|
|
247
|
+
standalone (``defer`` False) the use was already consumed by the peek-
|
|
248
|
+
less lookup, so the line is final.
|
|
249
|
+
"""
|
|
250
|
+
out = {"additionalContext": line}
|
|
251
|
+
if defer:
|
|
252
|
+
out["_on_allow"] = consume
|
|
253
|
+
return out
|
|
254
|
+
|
|
255
|
+
|
|
224
256
|
def _confirm_request(base: str, denial, *, tool: str, op: str, path: str,
|
|
225
257
|
session_id: str) -> tuple:
|
|
226
258
|
"""Auto-file an Override Request for a ``confirm`` denial (S8).
|
|
@@ -349,7 +381,68 @@ def _scan_shell(cmd: str, base: str):
|
|
|
349
381
|
return None, ""
|
|
350
382
|
|
|
351
383
|
|
|
352
|
-
def
|
|
384
|
+
def _scan_shell_writes(cmd: str, base: str, session_id: str,
|
|
385
|
+
defer: bool) -> dict | None:
|
|
386
|
+
"""WRITE-class verdict for the files a shell command writes (v2.102.0).
|
|
387
|
+
|
|
388
|
+
``_scan_shell`` evaluates every token as a READ. Confirm holds, read_only
|
|
389
|
+
rules and the builtin write-denies are all write-class, so ``cat >>
|
|
390
|
+
CLAUDE.md``, ``sed -i`` on .mcp.json or a heredoc into a hook body sailed
|
|
391
|
+
through with no hold — the one bypass docs/confirm-guard.md could only
|
|
392
|
+
ask the agent not to use. Targets come from the extractor the edit
|
|
393
|
+
ledger already trusts after the fact (cli/_shell_writes), with the same
|
|
394
|
+
best-effort caveat: a missed target costs a missed hold, a false one
|
|
395
|
+
costs a hold the human clears in one tap. Synthetic spelling denials are
|
|
396
|
+
skipped exactly as the read scan skips them (a token that is not a path
|
|
397
|
+
must not veto the command); the corrupt-config deny-all still refuses.
|
|
398
|
+
|
|
399
|
+
Grant identity is ``tool="Bash", op="write", path=<target>`` — bound to
|
|
400
|
+
the file, not the command text, the same stated limitation as the
|
|
401
|
+
shell_warn layer. Several held targets need several grants; the command
|
|
402
|
+
runs only when every one is covered, and the uses are burned together
|
|
403
|
+
(or not at all) by the dispatcher's settle step.
|
|
404
|
+
"""
|
|
405
|
+
if shell_write_targets is None:
|
|
406
|
+
return None
|
|
407
|
+
lines, consumers = [], []
|
|
408
|
+
for target in shell_write_targets(cmd, base):
|
|
409
|
+
denial = ag.check(target, "write", base)
|
|
410
|
+
if denial is None:
|
|
411
|
+
continue
|
|
412
|
+
if denial.rule.startswith("<") and denial.rule != "<corrupt-config>":
|
|
413
|
+
continue
|
|
414
|
+
granted = _override_allows(base, denial, tool="Bash", op="write",
|
|
415
|
+
path=target, session_id=session_id,
|
|
416
|
+
peek=defer)
|
|
417
|
+
if granted:
|
|
418
|
+
lines.append(granted)
|
|
419
|
+
if defer:
|
|
420
|
+
consumers.append(lambda t=target, d=denial: _override_allows(
|
|
421
|
+
base, d, tool="Bash", op="write", path=t,
|
|
422
|
+
session_id=session_id))
|
|
423
|
+
continue
|
|
424
|
+
_record(denial, "Bash", "write", target, base, session_id)
|
|
425
|
+
if denial.kind == "confirm":
|
|
426
|
+
rid, note = _confirm_request(base, denial, tool="Bash", op="write",
|
|
427
|
+
path=target, session_id=session_id)
|
|
428
|
+
return _deny(ag.refusal(denial, target, "write", surface="hook",
|
|
429
|
+
tool="Bash", request_id=rid,
|
|
430
|
+
request_note=note)
|
|
431
|
+
+ " (best-effort shell scan)")
|
|
432
|
+
return _deny(ag.refusal(denial, target, "write", surface="hook",
|
|
433
|
+
tool="Bash") + " (best-effort shell scan)")
|
|
434
|
+
if not lines:
|
|
435
|
+
return None
|
|
436
|
+
|
|
437
|
+
def _settle():
|
|
438
|
+
got = [fn() for fn in consumers]
|
|
439
|
+
return "\n".join(got) if all(got) else None
|
|
440
|
+
|
|
441
|
+
return _granted("\n".join(lines), bool(consumers), _settle)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def run(payload: dict, project_path: Path | None = None,
|
|
445
|
+
defer_consume: bool = False) -> dict | None:
|
|
353
446
|
tool = normalize_tool_name(payload.get("tool_name", ""))
|
|
354
447
|
tool_input = payload.get("tool_input", {}) or {}
|
|
355
448
|
base = str(project_path if project_path is not None else Path.cwd())
|
|
@@ -363,9 +456,13 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
|
|
|
363
456
|
denial = ag.check(fp, op, base)
|
|
364
457
|
if denial:
|
|
365
458
|
granted = _override_allows(base, denial, tool=tool, op=op,
|
|
366
|
-
path=fp, session_id=session_id
|
|
459
|
+
path=fp, session_id=session_id,
|
|
460
|
+
peek=defer_consume)
|
|
367
461
|
if granted:
|
|
368
|
-
return
|
|
462
|
+
return _granted(granted, defer_consume,
|
|
463
|
+
lambda: _override_allows(
|
|
464
|
+
base, denial, tool=tool, op=op, path=fp,
|
|
465
|
+
session_id=session_id))
|
|
369
466
|
_record(denial, tool, op, fp, base, session_id)
|
|
370
467
|
if denial.kind == "confirm":
|
|
371
468
|
# A confirm hold files its own request (grants were consulted
|
|
@@ -401,6 +498,6 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
|
|
|
401
498
|
return _deny(ag.refusal(denial, tok, "read",
|
|
402
499
|
surface="hook", tool=tool)
|
|
403
500
|
+ " (best-effort shell scan)")
|
|
404
|
-
return
|
|
501
|
+
return _scan_shell_writes(cmd, base, session_id, defer_consume)
|
|
405
502
|
|
|
406
503
|
return None
|
|
@@ -36,6 +36,7 @@ emit a short "[c3:hook-error] ..." additionalContext warning so enforcement
|
|
|
36
36
|
cannot silently stop enforcing.
|
|
37
37
|
"""
|
|
38
38
|
import importlib
|
|
39
|
+
import inspect
|
|
39
40
|
import json
|
|
40
41
|
import sys
|
|
41
42
|
from pathlib import Path
|
|
@@ -166,6 +167,16 @@ def _routes(event: str, raw_tool: str, norm_tool: str, host: str = HOST_CLAUDE):
|
|
|
166
167
|
|
|
167
168
|
_RUN_CACHE: dict = {}
|
|
168
169
|
|
|
170
|
+
# PreToolUse sub-hooks that consult override grants. Under the dispatcher
|
|
171
|
+
# they are asked to DEFER consumption (`defer_consume=True`): each returns a
|
|
172
|
+
# provisional allow carrying an `_on_allow` callable, and `_settle_grants`
|
|
173
|
+
# runs those only once every sub-hook has voted allow. Before v2.102.0 the
|
|
174
|
+
# access guard consumed a grant first and a strict-mode discipline deny from
|
|
175
|
+
# the next sub-hook then won the merge — grant spent, nothing written, the
|
|
176
|
+
# user asked twice for one edit. The same hole ran the other way for a
|
|
177
|
+
# discipline grant followed by a policy deny.
|
|
178
|
+
_DEFER_CONSUME = {"hook_access_guard", "hook_pretool_enforce"}
|
|
179
|
+
|
|
169
180
|
# Modules whose failure must DENY rather than fall through (fail closed).
|
|
170
181
|
# Scoped to write-class tools + shell: a broken guard must not let mutations
|
|
171
182
|
# sail through, but read-class fail-open avoids bricking whole sessions.
|
|
@@ -189,6 +200,77 @@ def _fail_closed_deny(module_name: str, err: str) -> dict:
|
|
|
189
200
|
}
|
|
190
201
|
|
|
191
202
|
|
|
203
|
+
def _is_deny(out) -> bool:
|
|
204
|
+
hso = out.get("hookSpecificOutput") if isinstance(out, dict) else None
|
|
205
|
+
return isinstance(hso, dict) and hso.get("permissionDecision") == "deny"
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _call_run(run_fn, module_name: str, event: str, payload: dict,
|
|
209
|
+
project_path):
|
|
210
|
+
"""Invoke a sub-hook, asking the grant-aware ones to defer consumption.
|
|
211
|
+
|
|
212
|
+
Signature-checked rather than assumed: tests stub these modules with
|
|
213
|
+
two-argument lambdas, and a legacy single-file hook must keep working.
|
|
214
|
+
"""
|
|
215
|
+
if event == "pretool" and module_name in _DEFER_CONSUME:
|
|
216
|
+
try:
|
|
217
|
+
accepts = "defer_consume" in inspect.signature(run_fn).parameters
|
|
218
|
+
except (TypeError, ValueError):
|
|
219
|
+
accepts = False
|
|
220
|
+
if accepts:
|
|
221
|
+
return run_fn(payload, project_path, defer_consume=True)
|
|
222
|
+
return run_fn(payload, project_path)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _grant_lost_deny() -> dict:
|
|
226
|
+
return {
|
|
227
|
+
"hookSpecificOutput": {
|
|
228
|
+
"hookEventName": "PreToolUse",
|
|
229
|
+
"permissionDecision": "deny",
|
|
230
|
+
"permissionDecisionReason": (
|
|
231
|
+
"[c3-override:spent] the override grant that would have "
|
|
232
|
+
"allowed this call was used up between check and use (a "
|
|
233
|
+
"concurrent call spent it). The rule stands — ask again "
|
|
234
|
+
"with c3_override if it is still needed."
|
|
235
|
+
),
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _settle_grants(outputs: list) -> list:
|
|
241
|
+
"""Burn deferred grant uses — only when the merged verdict is allow.
|
|
242
|
+
|
|
243
|
+
Any deny among the outputs means the call will not run, so every
|
|
244
|
+
`_on_allow` is dropped unrun and the grants stay live for the retry. On
|
|
245
|
+
an allow each consumer runs; the line it returns replaces the
|
|
246
|
+
provisional one (it carries the post-consumption uses count). A consumer
|
|
247
|
+
that finds nothing left — the grant was spent by a concurrent call
|
|
248
|
+
between peek and settle — turns its allow into a deny: the call must
|
|
249
|
+
not proceed on a grant that no longer exists.
|
|
250
|
+
"""
|
|
251
|
+
denied = any(_is_deny(o) for o in outputs)
|
|
252
|
+
settled = []
|
|
253
|
+
for out in outputs:
|
|
254
|
+
if not isinstance(out, dict):
|
|
255
|
+
settled.append(out)
|
|
256
|
+
continue
|
|
257
|
+
consume = out.pop("_on_allow", None)
|
|
258
|
+
if consume is None or denied:
|
|
259
|
+
settled.append(out)
|
|
260
|
+
continue
|
|
261
|
+
try:
|
|
262
|
+
line = consume()
|
|
263
|
+
except Exception as exc:
|
|
264
|
+
log_hook_error("hook_dispatch:settle", exc)
|
|
265
|
+
line = None
|
|
266
|
+
if line:
|
|
267
|
+
out["additionalContext"] = str(line)
|
|
268
|
+
settled.append(out)
|
|
269
|
+
else:
|
|
270
|
+
settled.append(_grant_lost_deny())
|
|
271
|
+
return settled
|
|
272
|
+
|
|
273
|
+
|
|
192
274
|
def _load_run(module_name: str):
|
|
193
275
|
"""Import cli.<module_name> and return its run() (cached).
|
|
194
276
|
|
|
@@ -353,7 +435,7 @@ def dispatch(event: str, payload: dict, project_path: Path | None = None) -> dic
|
|
|
353
435
|
outputs.append(_fail_closed_deny(module_name, err))
|
|
354
436
|
continue
|
|
355
437
|
try:
|
|
356
|
-
out = run_fn
|
|
438
|
+
out = _call_run(run_fn, module_name, event, payload, project_path)
|
|
357
439
|
if out:
|
|
358
440
|
outputs.append(out)
|
|
359
441
|
except Exception as exc:
|
|
@@ -368,7 +450,15 @@ def dispatch(event: str, payload: dict, project_path: Path | None = None) -> dic
|
|
|
368
450
|
# Critical state-layer warnings (corrupt enforcement_state.json)
|
|
369
451
|
# become visible instead of silently disabling enforcement.
|
|
370
452
|
warnings.extend(_hook_utils.drain_state_warnings())
|
|
453
|
+
# A PreToolUse deny is final — merge keeps the first deny and no
|
|
454
|
+
# allow readmits it — so the sub-hooks after it have nothing to add,
|
|
455
|
+
# and running them could only spend a grant or log a block for a
|
|
456
|
+
# call that will not happen.
|
|
457
|
+
if event == "pretool" and outputs and _is_deny(outputs[-1]):
|
|
458
|
+
break
|
|
371
459
|
|
|
460
|
+
if event == "pretool":
|
|
461
|
+
outputs = _settle_grants(outputs)
|
|
372
462
|
return merge_outputs(outputs, warnings, event=event, host=host)
|
|
373
463
|
|
|
374
464
|
|
|
@@ -504,11 +504,12 @@ def _agent_cannot_comply(payload: dict, base: Path) -> str | None:
|
|
|
504
504
|
|
|
505
505
|
|
|
506
506
|
def _override_allows(base: Path, tool_name: str, tool_input: dict,
|
|
507
|
-
session_id: str) -> str | None:
|
|
507
|
+
session_id: str, peek: bool = False) -> str | None:
|
|
508
508
|
"""A live discipline grant for this exact write, or None (spec §5).
|
|
509
509
|
|
|
510
510
|
Runs AFTER `_vault_denial`, which stays unconditional: no grant, no
|
|
511
511
|
config value and no approval can open a native write path to the vault.
|
|
512
|
+
``peek`` looks without consuming (see hook_dispatch's settle step).
|
|
512
513
|
"""
|
|
513
514
|
target = str(tool_input.get("file_path")
|
|
514
515
|
or tool_input.get("notebook_path") or "")
|
|
@@ -517,16 +518,23 @@ def _override_allows(base: Path, tool_name: str, tool_input: dict,
|
|
|
517
518
|
try:
|
|
518
519
|
from services import override_grants as og # noqa: PLC0415 — lazy
|
|
519
520
|
return og.gate_discipline(base, tool=tool_name, path=target,
|
|
520
|
-
session_id=session_id)
|
|
521
|
+
session_id=session_id, peek=peek)
|
|
521
522
|
except Exception:
|
|
522
523
|
return None # fail closed: no grant, ordinary block
|
|
523
524
|
|
|
524
525
|
|
|
525
|
-
def run(payload: dict, project_path: Path | None = None
|
|
526
|
+
def run(payload: dict, project_path: Path | None = None,
|
|
527
|
+
defer_consume: bool = False) -> dict | None:
|
|
526
528
|
"""Core enforcement logic — importable by the dispatcher and tests.
|
|
527
529
|
|
|
528
530
|
Returns a hook-output dict ({"additionalContext": ...} or a deny
|
|
529
531
|
{"hookSpecificOutput": ...}) or None when the call passes silently.
|
|
532
|
+
|
|
533
|
+
``defer_consume`` (dispatcher only): a discipline grant is peeked, not
|
|
534
|
+
burned, and the output carries an ``_on_allow`` callable the dispatcher
|
|
535
|
+
runs once the whole PreToolUse verdict is allow — so a policy deny from
|
|
536
|
+
hook_access_guard can no longer spend a grant on a write that never
|
|
537
|
+
happens (v2.102.0).
|
|
530
538
|
"""
|
|
531
539
|
tool_name = normalize_tool_name(payload.get("tool_name", ""))
|
|
532
540
|
|
|
@@ -591,9 +599,20 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
|
|
|
591
599
|
# the ledger stays complete either way; what is lost is the pre-edit
|
|
592
600
|
# snapshot c3_edit would have taken.
|
|
593
601
|
if tool_name in blocked_tools and mode == "strict":
|
|
594
|
-
granted = _override_allows(base, tool_name, tool_input, session_id
|
|
602
|
+
granted = _override_allows(base, tool_name, tool_input, session_id,
|
|
603
|
+
peek=defer_consume)
|
|
595
604
|
if granted:
|
|
596
|
-
|
|
605
|
+
out = _with_warnings({"additionalContext": granted}, policy)
|
|
606
|
+
if defer_consume:
|
|
607
|
+
def _consume():
|
|
608
|
+
line = _override_allows(base, tool_name, tool_input,
|
|
609
|
+
session_id)
|
|
610
|
+
if not line:
|
|
611
|
+
return None
|
|
612
|
+
return _with_warnings({"additionalContext": line},
|
|
613
|
+
policy)["additionalContext"]
|
|
614
|
+
out["_on_allow"] = _consume
|
|
615
|
+
return out
|
|
597
616
|
cannot = _agent_cannot_comply(payload, base)
|
|
598
617
|
if cannot:
|
|
599
618
|
return _with_warnings({
|
|
@@ -832,7 +832,7 @@ async def c3_override(action: str = "list", path: str = "", tool: str = "",
|
|
|
832
832
|
layer: str = "", timeout_s: int = 60,
|
|
833
833
|
ctx: Context = None) -> str:
|
|
834
834
|
"""ASK A HUMAN to allow ONE blocked call — never a policy change.
|
|
835
|
-
actions: request, status, wait (blocks
|
|
835
|
+
actions: request, status, wait (blocks timeout_s: default 60, max 180), list, withdraw.
|
|
836
836
|
request: path = the exact blocked path; why = one concrete sentence for the human.
|
|
837
837
|
A yes mints a single-use, session-bound, path-exact grant with a short TTL; the
|
|
838
838
|
rule that blocked you stays in force. There is NO approve action here — only the
|
|
@@ -25,27 +25,43 @@ import os
|
|
|
25
25
|
def session_id(svc) -> str:
|
|
26
26
|
"""This agent's identity, for leases and for grants.
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
Prefers the HOST session id (``current_session["host_session_id"]``,
|
|
29
|
+
from ``CLAUDE_CODE_SESSION_ID`` — services.session_manager) over C3's
|
|
30
|
+
own timestamp id. The PreToolUse hooks file requests and consume grants
|
|
31
|
+
under the id the host puts in every hook payload, which is that same
|
|
32
|
+
value; until v2.102.0 this surface used C3's own id instead, so every
|
|
33
|
+
hook-filed confirm hold answered ``c3_override(action='wait')`` with
|
|
34
|
+
"belongs to another session" and the flow CLAUDE.md mandates could not
|
|
35
|
+
complete. One id on both surfaces is what makes it true.
|
|
36
|
+
|
|
37
|
+
Falls back to C3's session id, then to the process id, never to "". Two
|
|
38
|
+
agents that both resolved to "" would count as ONE session and stop
|
|
39
|
+
blocking each other — the exact opposite of what a lock is for. Each
|
|
40
|
+
Claude Code session runs its own c3-mcp process, so the pid is a
|
|
41
|
+
faithful stand-in.
|
|
32
42
|
|
|
33
43
|
It is also the right identity for c3_project: that proxy builds a runtime
|
|
34
44
|
for the TARGET project but runs inside the CALLER's process, so the pid
|
|
35
45
|
keeps the edit attributed to the agent that actually asked for it.
|
|
36
46
|
"""
|
|
37
47
|
session = getattr(getattr(svc, "session_mgr", None), "current_session", None) or {}
|
|
48
|
+
host = str(session.get("host_session_id") or "").strip()
|
|
49
|
+
if host:
|
|
50
|
+
return host
|
|
38
51
|
return str(session.get("id", "") or "") or f"pid-{os.getpid()}"
|
|
39
52
|
|
|
40
53
|
|
|
41
|
-
def allow(svc, denial, *, tool: str, op: str, path
|
|
54
|
+
def allow(svc, denial, *, tool: str, op: str, path,
|
|
55
|
+
peek: bool = False) -> str | None:
|
|
42
56
|
"""The `[c3-override:granted]` line when a live grant permits this exact
|
|
43
57
|
call, or None to stay denied.
|
|
44
58
|
|
|
45
59
|
Consumes a use on success — so callers must invoke this only when they
|
|
46
|
-
are about to proceed, never speculatively
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
are about to proceed, never speculatively; ``peek=True`` is the
|
|
61
|
+
speculative form (a look, no burn) for a caller that has to check
|
|
62
|
+
several targets before it knows whether the call runs at all. Ordering
|
|
63
|
+
inside `gate_access` is policy first, grants second, so a grant is void
|
|
64
|
+
the instant the policy or its layer is switched off (§12.8).
|
|
49
65
|
|
|
50
66
|
Fail-closed by construction: any error resolving the grant store leaves
|
|
51
67
|
the caller on its ordinary refusal path. A grant that cannot be read is
|
|
@@ -56,7 +72,8 @@ def allow(svc, denial, *, tool: str, op: str, path) -> str | None:
|
|
|
56
72
|
try:
|
|
57
73
|
from services import override_grants as og # noqa: PLC0415 — lazy
|
|
58
74
|
return og.gate_access(svc.project_path, denial, tool=tool, op=op,
|
|
59
|
-
path=str(path), session_id=session_id(svc)
|
|
75
|
+
path=str(path), session_id=session_id(svc),
|
|
76
|
+
peek=peek)
|
|
60
77
|
except Exception:
|
|
61
78
|
return None
|
|
62
79
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"""c3_artifacts — agent-config tracking: inventory, history, diff, restore."""
|
|
2
2
|
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from cli.tools import _grants
|
|
3
6
|
from services import access_guard
|
|
4
7
|
|
|
5
8
|
READ_ACTIONS = {"scan", "list", "history", "show", "diff", "status"}
|
|
@@ -139,8 +142,35 @@ def handle_artifacts(action, svc, finalize, *, artifact="", cls="", provider="",
|
|
|
139
142
|
if not version:
|
|
140
143
|
return done_resp("[artifacts:error] restore requires version.", "error")
|
|
141
144
|
session = getattr(getattr(svc, "session_mgr", None), "current_session", None) or {}
|
|
145
|
+
sid = session.get("id", "")
|
|
146
|
+
granted = ""
|
|
142
147
|
try:
|
|
143
|
-
|
|
148
|
+
try:
|
|
149
|
+
res = store.restore(artifact, version, session_id=sid,
|
|
150
|
+
confirm="hold")
|
|
151
|
+
except access_guard.AccessDenied as exc:
|
|
152
|
+
if exc.denial.kind != "confirm":
|
|
153
|
+
raise
|
|
154
|
+
# An agent-issued restore of a held file pauses like any
|
|
155
|
+
# other agent write to it (v2.102.0): the hold is filed
|
|
156
|
+
# against the artifact's ROOT so one approval covers a
|
|
157
|
+
# multi-file unit (a skill directory), and the retry — this
|
|
158
|
+
# same call — consumes that grant and restores under it.
|
|
159
|
+
entry = store.resolve(artifact) or {}
|
|
160
|
+
root = str(Path(str(svc.project_path))
|
|
161
|
+
/ str(entry.get("root") or artifact))
|
|
162
|
+
granted = _grants.allow(svc, exc.denial, tool="c3_artifacts",
|
|
163
|
+
op="write", path=root) or ""
|
|
164
|
+
if not granted:
|
|
165
|
+
rid, note = _grants.confirm_request(
|
|
166
|
+
svc, exc.denial, tool="c3_artifacts", op="write",
|
|
167
|
+
path=root)
|
|
168
|
+
return done_resp(
|
|
169
|
+
access_guard.refusal(exc.denial, root, "write",
|
|
170
|
+
request_id=rid, request_note=note),
|
|
171
|
+
"access-confirm")
|
|
172
|
+
res = store.restore(artifact, version, session_id=sid,
|
|
173
|
+
confirm="approved")
|
|
144
174
|
except access_guard.AccessDenied as exc:
|
|
145
175
|
# Service layer raises; the tool boundary converts to the S1/S2
|
|
146
176
|
# refusal string (docs/access-guard.md §3).
|
|
@@ -157,7 +187,8 @@ def handle_artifacts(action, svc, finalize, *, artifact="", cls="", provider="",
|
|
|
157
187
|
session_id=session.get("id", ""))
|
|
158
188
|
except Exception:
|
|
159
189
|
pass
|
|
160
|
-
lines = [
|
|
190
|
+
lines = ([granted] if granted else []) + [
|
|
191
|
+
f"[artifacts:restored] {res['id']} v{version} -> live as "
|
|
161
192
|
f"v{res['new_version']} — {len(res['files_written'])} file(s) written"
|
|
162
193
|
+ (f", {len(res['files_removed'])} removed" if res["files_removed"] else "")]
|
|
163
194
|
for w in res["warnings"]:
|