code-context-control 2.118.0__tar.gz → 2.118.2__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.118.0 → code_context_control-2.118.2}/PKG-INFO +1 -1
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/c3.py +1 -1
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_server.py +5 -3
- {code_context_control-2.118.0 → code_context_control-2.118.2}/code_context_control.egg-info/PKG-INFO +1 -1
- {code_context_control-2.118.0 → code_context_control-2.118.2}/code_context_control.egg-info/SOURCES.txt +2 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/mobile_api.py +6 -4
- {code_context_control-2.118.0 → code_context_control-2.118.2}/pyproject.toml +1 -1
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/access_guard.py +13 -5
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/agent_locks.py +10 -4
- code_context_control-2.118.2/services/atomic_json.py +118 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/shell_output.py +17 -3
- code_context_control-2.118.2/tests/test_atomic_json.py +240 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_shell_output.py +98 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/LICENSE +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/README.md +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/__init__.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/_hook_utils.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/_shell_writes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/commands/__init__.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/commands/common.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/commands/parser.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/docs.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/edits.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/guide/access.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/guide/bitbucket.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/guide/credentials.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/guide/getting-started.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/guide/index.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/guide/masking.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/guide/oracle.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/guide/shared.css +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/guide/tools.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/guide/workflow.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_access_guard.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_artifact.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_auto_snapshot.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_c3_signal.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_c3read.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_dispatch.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_edit_ledger.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_edit_unlock.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_filter.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_ghost_files.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_pretool_enforce.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_prompt_recall.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_read.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_session_stats.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hook_terse_advisor.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/app.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/add_project.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/config_editor.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/drill_artifacts.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/drill_health.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/drill_panel.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/drill_subprojects.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/drill_tasks.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/drill_views.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/global_search.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/hub_access.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/hub_ci.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/hub_cred_audit.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/hub_credentials.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/hub_enforcement.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/hub_locks.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/hub_tokens.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/mcp_manager.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/modals.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/project_card.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/project_tree.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/session_drawer.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/settings_modal.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/sidebar.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/summary_bar.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/task_board.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/toasts.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/components/topbar.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui/state.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/hub_ui.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/mcp_proxy.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/mcp_server.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/progress.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/server.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/__init__.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/_grants.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/_helpers.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/agent.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/artifacts.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/bitbucket.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/ci.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/compress.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/credentials.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/delegate.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/edit.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/edit_verify.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/edits.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/federate.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/filter.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/impact.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/jira.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/locks.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/memory.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/override.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/project.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/read.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/search.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/session.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/shell.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/shell_job.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/shell_nudge.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/shell_parsers.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/shell_render.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/status.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/tasks.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/tools/validate.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/api.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/app.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/access.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/bitbucket.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/chat.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/credentials.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/dashboard.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/edits.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/enforcement.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/instructions.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/jira.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/memory.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/sessions.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/settings.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/sidebar.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/components/tasks.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/icons.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/pm_shared.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/shared.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui/theme.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui_legacy.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/cli/ui_nano.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/code_context_control.egg-info/dependency_links.txt +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/code_context_control.egg-info/entry_points.txt +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/code_context_control.egg-info/requires.txt +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/code_context_control.egg-info/top_level.txt +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/core/__init__.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/core/config.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/core/ide.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/core/mcp_toml.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/core/web_security.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/__init__.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/config.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/mcp_oracle.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/oracle_server.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/oracle_ui.html +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/__init__.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/activity_reporter.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/api_auth.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/c3_bridge.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/chat_engine.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/chat_poll.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/chat_store.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/cross_memory.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/discovery_audit.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/federated_graph.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/health_checker.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/insight_engine.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/local_session.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/memory_reader.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/memory_writer.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/project_scanner.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/review_agent.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/tool_executor.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/services/tool_registry.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/activity.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/agents.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/app.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/busy.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/chat/conversations.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/chat/input.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/chat/markdown.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/chat/send.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/chat/stream_renderer.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/chat/toolbar.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/core.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/crossgraph.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/header.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/insights.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/projects.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/qrcode.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/settings.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/suggestions.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/oracle/ui/theme_tabs.js +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/__init__.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/access_telemetry.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/activity_log.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/agent_base.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/agents.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/artifact_defs.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/artifact_store.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/auto_memory.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/background_service.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/bench/__init__.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/bench/external/__init__.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/bench/external/aider_polyglot.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/bench/external/swe_bench.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/bench/search_eval.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/bench/shell_eval.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/benchmark_dashboard.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/bitbucket_client.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/bitbucket_credentials.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/ci_act.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/ci_cache.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/ci_expr.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/ci_failures.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/ci_github.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/ci_impact.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/ci_intel.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/ci_runner.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/ci_workflow.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/circuit_breaker.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/claude_md.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/compressor.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/context_snapshot.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/conversation_store.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/cred_audit.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/cred_telemetry.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/credential_store.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/doc_index.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/e2e_benchmark.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/e2e_evaluator.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/e2e_tasks.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/edit_ledger.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/embedding_index.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/enforcement_policy.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/error_reporting.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/file_memory.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/git_context.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/hub_service.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/index_store.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/indexer.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/jira_client.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/jira_cloud.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/jira_credentials.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/jira_data_center.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/jira_links.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/lexical_index.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/mask_activation.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/mask_mirror.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/mask_presets.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/memory.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/memory_consolidator.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/memory_distiller.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/memory_graph.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/memory_grounder.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/memory_queue.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/memory_scorer.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/metrics.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/notifications.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/ollama_bridge.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/ollama_client.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/ollama_credentials.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/oracle_service.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/output_filter.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/override_grants.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/override_policy.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/override_requests.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/override_wake.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/parser.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/project_manager.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/project_runtime.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/protocol.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/proxy_state.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/repo_map.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/repo_shape.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/reranker.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/retention.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/retrieval.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/retrieval_broker.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/router.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/runtime.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/scanner.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/session_benchmark.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/session_manager.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/session_preloader.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/shell_jobs.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/subprojects.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/task_store.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/telemetry.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/text_index.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/time_tracker.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/tool_classifier.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/transcript_index.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/validation_cache.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/vector_store.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/watcher.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/services/win_subprocess.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/setup.cfg +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_access_builtin_optout.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_access_guard.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_access_guard_meta.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_access_guard_shell_project.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_access_guard_surfaces.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_access_guard_wiring.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_activity_reporter.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_agent_locks.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_aider_polyglot.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_antigravity_transition.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_artifact_store.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_artifact_tool.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_background_service.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_bitbucket_cli_smoke.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_bitbucket_client.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_bitbucket_config_fallback.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_bitbucket_credentials.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_bitbucket_tool.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_builtin_modes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_c3_bridge_project_artifacts.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_c3_shell.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_chat_poll.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_ci_act.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_ci_cache.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_ci_expr.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_ci_impact.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_ci_intel.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_ci_runner.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_ci_workflow.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_circuit_breaker.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_claude_md_merge.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_claude_md_updater.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_cli_smoke.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_compressor_large_file.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_confirm_guard.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_confirm_guard_wiring.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_cooccurrence_bounds.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_cred_audit.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_cred_telemetry.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_credential_login_kind.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_credential_store.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_credential_ui_parity.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_credentials_routes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_credentials_tool.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_creds_cli_smoke.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_delegate_cascade.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_delegate_version_probe.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_e2e_benchmark.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_edit_ledger_deadline.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_edit_ledger_hook.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_edit_locking.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_edit_normalization.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_edit_verify.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_embedding_index_deadlock.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_enforcement_flip.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_enforcement_policy.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_enforcement_routes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_env_parser.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_failed_calls_do_not_unlock.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_federated_graph.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_filter_backoff.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_ghost_files.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_ghost_generation.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_git_branch_awareness.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hook_access_guard.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hook_codex_compat.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hook_dispatch.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hook_pretool_enforce.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hook_prompt_recall.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hook_smoke.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hook_state.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hook_utf8_stdio.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hub_credentials_route.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hub_credentials_routes_write.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hub_enforcement_routes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hub_inspect_api.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hub_locks_routes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hub_oracle_service_routes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hub_override_routes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hub_server_smoke.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_hub_subproject_links.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_ide_workflow_adaptation.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_index_cap_not_hardcoded.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_init_clear_preserves_user_content.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_init_scan.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_install_mcp_entrypoint.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_jira_cli_smoke.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_jira_client.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_jira_config_fallback.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_jira_credentials.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_jira_links.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_jira_routes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_jira_tool.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_lazy_store_init.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_mask_guard.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_mask_routes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_mask_surfaces.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_mcp_host_guard.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_mcp_memory_timeout.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_mcp_server_smoke.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_mcp_toml.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_memory_distiller.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_memory_graph_api.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_memory_queue.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_memory_system.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_mobile_api.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_mobile_extras.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_mobile_override_routes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_mobile_security_api.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_no_stale_model_pins.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_notification_dedup.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_notification_discipline.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_ollama_credentials.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_api_auth.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_apikey_api.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_c3_bridge.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_chat_engine.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_chat_store.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_discovery_api.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_discovery_audit.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_health_probe.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_local_auth.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_ollama_bridge.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_scanner_cache.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_security_fixes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_session_bootstrap.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_subproject_awareness.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_oracle_ui_bundle.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_output_filter.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_override_grants.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_override_grants_mcp.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_override_requests.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_override_wake.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_permissions.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_pm_api.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_project_manager.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_project_manager_merge.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_project_tool.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_read_coercion.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_read_edit_parity.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_related_facts.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_repo_map.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_repo_shape.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_response_boilerplate.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_retention.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_review_digest.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_search_eval.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_search_p1.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_search_p2.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_search_p2b.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_search_p3.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_search_p4.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_search_p5.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_service_durability.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_session_benchmark.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_session_budget.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_shell_budget_s1.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_shell_creds.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_shell_eval.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_shell_jobs_s3.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_shell_nudge_s4.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_shell_parsers_s2.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_shell_robustness.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_shell_telemetry_s0.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_shell_write_ledger.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_shell_writes.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_subproject_clear_scope.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_subproject_depth_consumers.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_subproject_exclusion.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_subproject_federation.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_subproject_linkage_api.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_subprojects.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_swe_bench.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_task_store.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_task_tool.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_time_tracker.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_token_telemetry.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_token_tracking.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_tool_registry.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_ui_jsx_syntax.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_upgrade_and_version.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_validate.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_vault_write_guard.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_version_sync.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_web_security.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tests/test_windows_reliability.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/__init__.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/backend.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/main.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/__init__.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/benchmark_view.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/claudemd_view.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/compress_view.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/index_view.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/init_view.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/mcp_view.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/optimize_view.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/pipe_view.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/projects_view.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/search_view.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/session_view.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/stats.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/screens/ui_view.py +0 -0
- {code_context_control-2.118.0 → code_context_control-2.118.2}/tui/theme.tcss +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-context-control
|
|
3
|
-
Version: 2.118.
|
|
3
|
+
Version: 2.118.2
|
|
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
|
|
@@ -32,6 +32,7 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
32
32
|
from core.ide import PROFILES, detect_ide, get_profile, load_ide_config, normalize_ide_name
|
|
33
33
|
from services import access_guard
|
|
34
34
|
from services.activity_log import ActivityLog
|
|
35
|
+
from services.atomic_json import write_json_atomic
|
|
35
36
|
from services.project_manager import ProjectManager
|
|
36
37
|
from services.tool_classifier import CATEGORIES
|
|
37
38
|
|
|
@@ -3078,9 +3079,10 @@ def api_projects_config_put():
|
|
|
3078
3079
|
else:
|
|
3079
3080
|
merged[key] = val
|
|
3080
3081
|
cfg[section] = merged
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3082
|
+
# Not `config.json.tmp`: the CLI (services.access_guard) and the mobile
|
|
3083
|
+
# API write this same file, and the hub itself is threaded — one shared
|
|
3084
|
+
# temp name is a name they all pick at once. See services.atomic_json.
|
|
3085
|
+
write_json_atomic(cfg_file, cfg, trailing_newline=False)
|
|
3084
3086
|
try:
|
|
3085
3087
|
ActivityLog(str(resolved)).log("hub_config_write", {
|
|
3086
3088
|
"section": section, "keys": sorted(values.keys()), "source": "hub"})
|
{code_context_control-2.118.0 → code_context_control-2.118.2}/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.118.
|
|
3
|
+
Version: 2.118.2
|
|
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
|
|
@@ -189,6 +189,7 @@ services/agent_locks.py
|
|
|
189
189
|
services/agents.py
|
|
190
190
|
services/artifact_defs.py
|
|
191
191
|
services/artifact_store.py
|
|
192
|
+
services/atomic_json.py
|
|
192
193
|
services/auto_memory.py
|
|
193
194
|
services/background_service.py
|
|
194
195
|
services/benchmark_dashboard.py
|
|
@@ -299,6 +300,7 @@ tests/test_aider_polyglot.py
|
|
|
299
300
|
tests/test_antigravity_transition.py
|
|
300
301
|
tests/test_artifact_store.py
|
|
301
302
|
tests/test_artifact_tool.py
|
|
303
|
+
tests/test_atomic_json.py
|
|
302
304
|
tests/test_background_service.py
|
|
303
305
|
tests/test_bitbucket_cli_smoke.py
|
|
304
306
|
tests/test_bitbucket_client.py
|
|
@@ -56,6 +56,7 @@ from oracle.config import ORACLE_DIR, load_config
|
|
|
56
56
|
from oracle.services import api_auth, discovery_audit
|
|
57
57
|
from oracle.services.api_auth import extract_bearer
|
|
58
58
|
from oracle.services.tool_registry import _c3_version
|
|
59
|
+
from services.atomic_json import write_json_atomic
|
|
59
60
|
|
|
60
61
|
# 2: /info reports EFFECTIVE capabilities (filtered by the config switches)
|
|
61
62
|
# rather than a static list, so a client can gate its UI instead of probing.
|
|
@@ -2090,10 +2091,11 @@ def _write_override_section(project: Path, section: dict) -> dict:
|
|
|
2090
2091
|
else:
|
|
2091
2092
|
block[key] = value
|
|
2092
2093
|
cfg["override"] = block
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2094
|
+
# Shared with the CLI and the hub, and served from a threaded API, so the
|
|
2095
|
+
# publish goes through the one race-safe writer rather than a temp name
|
|
2096
|
+
# every writer of this file would pick. See services.atomic_json.
|
|
2097
|
+
write_json_atomic(cfg_file, cfg, ensure_ascii=False,
|
|
2098
|
+
trailing_newline=False)
|
|
2097
2099
|
return block
|
|
2098
2100
|
|
|
2099
2101
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code-context-control"
|
|
7
|
-
version = "2.118.
|
|
7
|
+
version = "2.118.2"
|
|
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"
|
|
@@ -20,6 +20,8 @@ import re
|
|
|
20
20
|
from dataclasses import dataclass
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
|
|
23
|
+
from services.atomic_json import write_json_atomic
|
|
24
|
+
|
|
23
25
|
# ── Rule model ──────────────────────────────────────────────────────────────
|
|
24
26
|
|
|
25
27
|
_KIND_DENY = "deny"
|
|
@@ -1473,11 +1475,17 @@ def _load_config_for_write(cfg: Path) -> tuple:
|
|
|
1473
1475
|
|
|
1474
1476
|
|
|
1475
1477
|
def _write_scope_config(cfg: Path, data: dict) -> None:
|
|
1476
|
-
"""Atomic same-directory replace; privileged internal write.
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1478
|
+
"""Atomic same-directory replace; privileged internal write.
|
|
1479
|
+
|
|
1480
|
+
The CLI, the hub and the mobile API all reach this for the same scope
|
|
1481
|
+
file, so the publish is via ``services.atomic_json`` — a shared
|
|
1482
|
+
``config.json.tmp`` is a name they would collide on. The fsync matters
|
|
1483
|
+
more here than anywhere else in the codebase: a truncated ``config.json``
|
|
1484
|
+
comes back from ``load_all`` in its ``corrupt`` list, every path then
|
|
1485
|
+
fails closed with ``<corrupt-config>``, and the session can no longer run
|
|
1486
|
+
the command that would fix it.
|
|
1487
|
+
"""
|
|
1488
|
+
write_json_atomic(cfg, data)
|
|
1481
1489
|
|
|
1482
1490
|
|
|
1483
1491
|
def seed_default_global_rules() -> dict:
|
|
@@ -41,6 +41,7 @@ import time
|
|
|
41
41
|
import uuid
|
|
42
42
|
from pathlib import Path
|
|
43
43
|
|
|
44
|
+
from services.atomic_json import write_json_atomic
|
|
44
45
|
from services.task_store import _FileLock
|
|
45
46
|
|
|
46
47
|
SCHEMA_VERSION = 1
|
|
@@ -219,10 +220,15 @@ class LockStore:
|
|
|
219
220
|
return doc
|
|
220
221
|
|
|
221
222
|
def _save(self, doc: dict) -> None:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
# The `_FileLock` guard serialises the read-modify-write, but only
|
|
224
|
+
# among callers that take it — and it is advisory, held per process.
|
|
225
|
+
# The publish itself has to be safe on its own: a shared
|
|
226
|
+
# `locks.json.tmp` is one name every writer on the box picks, and two
|
|
227
|
+
# of them landing together either publishes a half-written lease file
|
|
228
|
+
# or raises WinError 5 (see services.atomic_json). Losing this write
|
|
229
|
+
# means losing live leases, which is exactly what `_load` refuses to
|
|
230
|
+
# do when it quarantines a corrupt file instead of resetting it.
|
|
231
|
+
write_json_atomic(self.state_file, doc)
|
|
226
232
|
|
|
227
233
|
def _guard(self):
|
|
228
234
|
return _FileLock(self.lock_file)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""One durable same-directory replace for the small JSON stores C3 writes.
|
|
2
|
+
|
|
3
|
+
Why this module exists
|
|
4
|
+
----------------------
|
|
5
|
+
C3 keeps a handful of tiny JSON files that more than one writer touches: the
|
|
6
|
+
agent lock store, the per-scope ``config.json`` written by the CLI, the hub
|
|
7
|
+
and the mobile API, and the shell job store. Every one of them had grown the
|
|
8
|
+
same three-line publish step by copy — ``<name>.tmp`` then a single
|
|
9
|
+
``os.replace`` — and on Windows that shape is a live bug rather than a style
|
|
10
|
+
nit:
|
|
11
|
+
|
|
12
|
+
1. **A shared temp name races on the TEMP, not on the target.**
|
|
13
|
+
``os.replace`` is atomic on the destination; nothing at all protects
|
|
14
|
+
``<name>.tmp``. Two writers of one path both create it, so one publishes
|
|
15
|
+
a file the other is still writing, or the replace raises
|
|
16
|
+
``PermissionError: [WinError 5] Access is denied`` because the loser still
|
|
17
|
+
holds a handle to it. Observed for real in the shell job store, where it
|
|
18
|
+
crashed a supervisor and reported "failed before running" for a command
|
|
19
|
+
that was fine (fixed there in 2.118.1, PR #157). The other copies had the
|
|
20
|
+
identical shape and no demonstrated failure only because they are written
|
|
21
|
+
far less often — the lock store especially is a coin that had not landed
|
|
22
|
+
on its edge yet.
|
|
23
|
+
2. **A pid alone is not a unique suffix.** Threads share a pid, and the hub,
|
|
24
|
+
the Oracle and the lock store are all written from threaded servers. Six
|
|
25
|
+
threads are enough to drive a ``.tmp<pid>`` implementation into both
|
|
26
|
+
``PermissionError`` and ``FileNotFoundError`` within a second, so the
|
|
27
|
+
suffix carries per-WRITE randomness as well.
|
|
28
|
+
3. **One attempt turns a transient sharing violation into a lost write.** An
|
|
29
|
+
AV scanner or the Search indexer holding the target open for a few
|
|
30
|
+
milliseconds is ordinary on Windows and clears on its own; a single
|
|
31
|
+
``os.replace`` turns it into an exception at the call site.
|
|
32
|
+
|
|
33
|
+
``cli/_hook_utils._atomic_write_json`` deliberately does NOT import this. A
|
|
34
|
+
PreToolUse hook is a separate, short-lived, single-threaded process that
|
|
35
|
+
imports nothing from ``services`` on purpose, so pid-only is genuinely
|
|
36
|
+
enough there. That one duplication stays.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import json
|
|
42
|
+
import os
|
|
43
|
+
import secrets
|
|
44
|
+
import time
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
|
|
47
|
+
# Four attempts across ~0.14s of backoff. Sized for a scanner or indexer
|
|
48
|
+
# holding a handle, not for a lock a peer holds for seconds — a failure that
|
|
49
|
+
# survives this is real and is re-raised.
|
|
50
|
+
REPLACE_ATTEMPTS = 4
|
|
51
|
+
REPLACE_BACKOFF_S = 0.02
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def atomic_tmp_path(path: Path) -> Path:
|
|
55
|
+
"""``<name>.tmp<pid>-<random>`` beside ``path`` — unique per write."""
|
|
56
|
+
return path.with_name(f"{path.name}.tmp{os.getpid()}-{secrets.token_hex(4)}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def write_text_atomic(path, text: str, *, encoding: str = "utf-8",
|
|
60
|
+
fsync: bool = True) -> None:
|
|
61
|
+
"""Publish ``text`` at ``path``: unique temp, then a retried ``os.replace``.
|
|
62
|
+
|
|
63
|
+
``fsync`` flushes the temp's bytes before the replace, so a crash or power
|
|
64
|
+
loss between the two cannot publish a zero-length or half-written file.
|
|
65
|
+
Durable state wants that: a truncated ``config.json`` makes the Access
|
|
66
|
+
Guard read ``<corrupt-config>`` and fail closed on every subsequent call,
|
|
67
|
+
which wedges the whole session. Callers whose file is ephemeral spill
|
|
68
|
+
state pass ``fsync=False`` rather than pay a disk flush per update.
|
|
69
|
+
|
|
70
|
+
Newline translation matches ``Path.write_text`` (the default), so this is
|
|
71
|
+
a drop-in for the ``write_text`` + ``os.replace`` pairs it replaces and
|
|
72
|
+
does not churn line endings in files already on disk.
|
|
73
|
+
"""
|
|
74
|
+
path = Path(path)
|
|
75
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
tmp = atomic_tmp_path(path)
|
|
77
|
+
try:
|
|
78
|
+
with open(tmp, "w", encoding=encoding) as fh:
|
|
79
|
+
fh.write(text)
|
|
80
|
+
if fsync:
|
|
81
|
+
fh.flush()
|
|
82
|
+
os.fsync(fh.fileno())
|
|
83
|
+
last_exc: OSError | None = None
|
|
84
|
+
for attempt in range(REPLACE_ATTEMPTS):
|
|
85
|
+
try:
|
|
86
|
+
os.replace(tmp, path)
|
|
87
|
+
return
|
|
88
|
+
except PermissionError as exc:
|
|
89
|
+
last_exc = exc
|
|
90
|
+
if attempt < REPLACE_ATTEMPTS - 1:
|
|
91
|
+
time.sleep(REPLACE_BACKOFF_S * (2 ** attempt))
|
|
92
|
+
if last_exc is not None:
|
|
93
|
+
raise last_exc
|
|
94
|
+
finally:
|
|
95
|
+
# A successful replace already consumed tmp, so this only fires on the
|
|
96
|
+
# failure paths — otherwise abandoned temps accumulate next to the
|
|
97
|
+
# file, and for `.c3/` that means they get indexed and committed.
|
|
98
|
+
try:
|
|
99
|
+
if tmp.exists():
|
|
100
|
+
tmp.unlink()
|
|
101
|
+
except OSError:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def write_json_atomic(path, data, *, indent: int = 2,
|
|
106
|
+
ensure_ascii: bool = True,
|
|
107
|
+
trailing_newline: bool = True,
|
|
108
|
+
fsync: bool = True) -> None:
|
|
109
|
+
"""``write_text_atomic`` over ``json.dumps``.
|
|
110
|
+
|
|
111
|
+
The serialisation knobs exist so each caller keeps the exact bytes it
|
|
112
|
+
wrote before this module: the point of the change is the publish step,
|
|
113
|
+
not a reformat of five JSON files that other tools and diffs already read.
|
|
114
|
+
"""
|
|
115
|
+
text = json.dumps(data, indent=indent, ensure_ascii=ensure_ascii)
|
|
116
|
+
if trailing_newline:
|
|
117
|
+
text += "\n"
|
|
118
|
+
write_text_atomic(path, text, fsync=fsync)
|
|
@@ -75,6 +75,8 @@ from dataclasses import dataclass, field
|
|
|
75
75
|
from datetime import datetime, timedelta, timezone
|
|
76
76
|
from pathlib import Path
|
|
77
77
|
|
|
78
|
+
from services.atomic_json import write_json_atomic
|
|
79
|
+
|
|
78
80
|
__all__ = [
|
|
79
81
|
"STORE_ROOT", "RETENTION_DAYS", "RETENTION_BYTES", "HEAD_BYTES", "TAIL_BYTES",
|
|
80
82
|
"TEXT_MAX_BYTES", "DEFAULT_READ_BYTES", "LINE_CLIP_CHARS",
|
|
@@ -961,9 +963,21 @@ def _load_meta(path: Path) -> OutputMeta | None:
|
|
|
961
963
|
|
|
962
964
|
|
|
963
965
|
def _write_json_atomic(path: Path, data: dict) -> None:
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
os.replace
|
|
966
|
+
"""Publish a job record durably — see ``services.atomic_json``.
|
|
967
|
+
|
|
968
|
+
This was the first copy of the unique-temp + retried-``os.replace`` shape
|
|
969
|
+
(2.118.1, PR #157, after a shared ``<name>.tmp`` crashed supervisors on
|
|
970
|
+
Windows CI with WinError 5 and reported "failed before running" for
|
|
971
|
+
commands that were fine). The implementation now lives in one place so the
|
|
972
|
+
lock store and the three ``config.json`` writers share it instead of
|
|
973
|
+
carrying four more copies of a bug.
|
|
974
|
+
|
|
975
|
+
``fsync=False`` is the one thing kept local: a job record is ephemeral
|
|
976
|
+
spill state that a crash invalidates anyway, so a disk flush per status
|
|
977
|
+
update buys nothing. The durable stores take the default.
|
|
978
|
+
"""
|
|
979
|
+
write_json_atomic(path, data, indent=1, ensure_ascii=False,
|
|
980
|
+
trailing_newline=False, fsync=False)
|
|
967
981
|
|
|
968
982
|
|
|
969
983
|
def _move(src: Path, dst: Path) -> None:
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""services.atomic_json — the one publish step for C3's small JSON stores.
|
|
2
|
+
|
|
3
|
+
Every test here fails against the shape these call sites carried before
|
|
4
|
+
(`<name>.tmp` + a single `os.replace`), which is the shape that crashed shell
|
|
5
|
+
job supervisors on Windows with `PermissionError: [WinError 5]`. The point of
|
|
6
|
+
the module is that the lock store and the three `config.json` writers stop
|
|
7
|
+
re-deriving it, so the last class checks each of those call sites actually
|
|
8
|
+
routes through it rather than only the helper being correct in isolation.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import tempfile
|
|
17
|
+
import threading
|
|
18
|
+
import unittest
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from unittest.mock import patch
|
|
21
|
+
|
|
22
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
23
|
+
|
|
24
|
+
from services import atomic_json as aj # noqa: E402
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestTempNaming(unittest.TestCase):
|
|
28
|
+
"""The temp must be unique per WRITE, not per path and not per process."""
|
|
29
|
+
|
|
30
|
+
def setUp(self):
|
|
31
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
32
|
+
self.path = Path(self._tmp.name) / "config.json"
|
|
33
|
+
|
|
34
|
+
def tearDown(self):
|
|
35
|
+
self._tmp.cleanup()
|
|
36
|
+
|
|
37
|
+
def _replace_spy(self):
|
|
38
|
+
seen: list[str] = []
|
|
39
|
+
real = os.replace
|
|
40
|
+
|
|
41
|
+
def spy(src, dst):
|
|
42
|
+
seen.append(Path(src).name)
|
|
43
|
+
return real(src, dst)
|
|
44
|
+
|
|
45
|
+
return seen, spy
|
|
46
|
+
|
|
47
|
+
def test_temp_is_not_the_shared_name(self):
|
|
48
|
+
seen, spy = self._replace_spy()
|
|
49
|
+
with patch.object(aj.os, "replace", spy):
|
|
50
|
+
aj.write_json_atomic(self.path, {"a": 1})
|
|
51
|
+
self.assertEqual(len(seen), 1)
|
|
52
|
+
self.assertNotEqual(seen[0], "config.json.tmp",
|
|
53
|
+
"shared temp name — concurrent writers race on it")
|
|
54
|
+
self.assertTrue(seen[0].startswith("config.json.tmp"))
|
|
55
|
+
self.assertIn(str(os.getpid()), seen[0])
|
|
56
|
+
|
|
57
|
+
def test_temp_differs_between_writes_in_one_process(self):
|
|
58
|
+
"""Threads share a pid, so `.tmp<pid>` collides exactly as `.tmp` did."""
|
|
59
|
+
seen, spy = self._replace_spy()
|
|
60
|
+
with patch.object(aj.os, "replace", spy):
|
|
61
|
+
for i in range(5):
|
|
62
|
+
aj.write_json_atomic(self.path, {"a": i})
|
|
63
|
+
self.assertEqual(len(set(seen)), 5, f"temp name reused: {seen}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TestReplaceRetry(unittest.TestCase):
|
|
67
|
+
def setUp(self):
|
|
68
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
69
|
+
self.path = Path(self._tmp.name) / "config.json"
|
|
70
|
+
|
|
71
|
+
def tearDown(self):
|
|
72
|
+
self._tmp.cleanup()
|
|
73
|
+
|
|
74
|
+
def test_transient_permission_error_is_retried(self):
|
|
75
|
+
calls = {"n": 0}
|
|
76
|
+
real = os.replace
|
|
77
|
+
|
|
78
|
+
def flaky(src, dst):
|
|
79
|
+
calls["n"] += 1
|
|
80
|
+
if calls["n"] < 3: # two transient failures, then success
|
|
81
|
+
raise PermissionError(5, "Access is denied")
|
|
82
|
+
return real(src, dst)
|
|
83
|
+
|
|
84
|
+
with patch.object(aj.os, "replace", flaky):
|
|
85
|
+
aj.write_json_atomic(self.path, {"ok": True})
|
|
86
|
+
self.assertEqual(calls["n"], 3)
|
|
87
|
+
self.assertEqual(json.loads(self.path.read_text(encoding="utf-8")),
|
|
88
|
+
{"ok": True})
|
|
89
|
+
|
|
90
|
+
def test_persistent_permission_error_still_raises(self):
|
|
91
|
+
"""Retrying must not turn a real failure into a silently lost write."""
|
|
92
|
+
def always(src, dst):
|
|
93
|
+
raise PermissionError(5, "Access is denied")
|
|
94
|
+
|
|
95
|
+
with patch.object(aj.os, "replace", always):
|
|
96
|
+
with self.assertRaises(PermissionError):
|
|
97
|
+
aj.write_json_atomic(self.path, {"a": 1})
|
|
98
|
+
|
|
99
|
+
def test_a_non_permission_oserror_is_not_retried(self):
|
|
100
|
+
"""Only the Windows sharing violation is transient; the rest surface."""
|
|
101
|
+
calls = {"n": 0}
|
|
102
|
+
|
|
103
|
+
def broken(src, dst):
|
|
104
|
+
calls["n"] += 1
|
|
105
|
+
raise OSError(22, "Invalid argument")
|
|
106
|
+
|
|
107
|
+
with patch.object(aj.os, "replace", broken):
|
|
108
|
+
with self.assertRaises(OSError):
|
|
109
|
+
aj.write_json_atomic(self.path, {"a": 1})
|
|
110
|
+
self.assertEqual(calls["n"], 1)
|
|
111
|
+
|
|
112
|
+
def test_no_temp_is_left_behind_when_replace_fails(self):
|
|
113
|
+
def always(src, dst):
|
|
114
|
+
raise PermissionError(5, "Access is denied")
|
|
115
|
+
|
|
116
|
+
with patch.object(aj.os, "replace", always):
|
|
117
|
+
with self.assertRaises(PermissionError):
|
|
118
|
+
aj.write_json_atomic(self.path, {"a": 1})
|
|
119
|
+
leftovers = list(self.path.parent.glob("config.json.tmp*"))
|
|
120
|
+
self.assertEqual(leftovers, [], f"orphaned temps: {leftovers}")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class TestSerialisationIsPreserved(unittest.TestCase):
|
|
124
|
+
"""Each call site keeps the exact bytes it wrote before the refactor."""
|
|
125
|
+
|
|
126
|
+
def setUp(self):
|
|
127
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
128
|
+
self.path = Path(self._tmp.name) / "f.json"
|
|
129
|
+
|
|
130
|
+
def tearDown(self):
|
|
131
|
+
self._tmp.cleanup()
|
|
132
|
+
|
|
133
|
+
def test_default_is_indent_2_with_trailing_newline(self):
|
|
134
|
+
aj.write_json_atomic(self.path, {"a": 1})
|
|
135
|
+
self.assertEqual(self.path.read_text(encoding="utf-8"),
|
|
136
|
+
'{\n "a": 1\n}\n')
|
|
137
|
+
|
|
138
|
+
def test_trailing_newline_can_be_suppressed(self):
|
|
139
|
+
aj.write_json_atomic(self.path, {"a": 1}, trailing_newline=False)
|
|
140
|
+
self.assertEqual(self.path.read_text(encoding="utf-8"),
|
|
141
|
+
'{\n "a": 1\n}')
|
|
142
|
+
|
|
143
|
+
def test_ensure_ascii_false_keeps_non_ascii_literal(self):
|
|
144
|
+
aj.write_json_atomic(self.path, {"k": "café"}, ensure_ascii=False)
|
|
145
|
+
self.assertIn("café", self.path.read_text(encoding="utf-8"))
|
|
146
|
+
|
|
147
|
+
def test_parent_directory_is_created(self):
|
|
148
|
+
deep = Path(self._tmp.name) / "a" / "b" / "c.json"
|
|
149
|
+
aj.write_json_atomic(deep, {"a": 1})
|
|
150
|
+
self.assertEqual(json.loads(deep.read_text(encoding="utf-8")), {"a": 1})
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class TestFsync(unittest.TestCase):
|
|
154
|
+
def setUp(self):
|
|
155
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
156
|
+
self.path = Path(self._tmp.name) / "f.json"
|
|
157
|
+
|
|
158
|
+
def tearDown(self):
|
|
159
|
+
self._tmp.cleanup()
|
|
160
|
+
|
|
161
|
+
def test_durable_by_default(self):
|
|
162
|
+
"""A truncated config.json fails the guard closed; flush before publish."""
|
|
163
|
+
with patch.object(aj.os, "fsync") as fs:
|
|
164
|
+
aj.write_json_atomic(self.path, {"a": 1})
|
|
165
|
+
self.assertEqual(fs.call_count, 1)
|
|
166
|
+
|
|
167
|
+
def test_opt_out_for_ephemeral_state(self):
|
|
168
|
+
with patch.object(aj.os, "fsync") as fs:
|
|
169
|
+
aj.write_json_atomic(self.path, {"a": 1}, fsync=False)
|
|
170
|
+
fs.assert_not_called()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class TestConcurrency(unittest.TestCase):
|
|
174
|
+
def test_threads_never_publish_a_truncated_file(self):
|
|
175
|
+
"""The end-to-end property, and the one `.tmp<pid>` fails."""
|
|
176
|
+
with tempfile.TemporaryDirectory() as td:
|
|
177
|
+
path = Path(td) / "locks.json"
|
|
178
|
+
errors: list[Exception] = []
|
|
179
|
+
|
|
180
|
+
def writer(i):
|
|
181
|
+
try:
|
|
182
|
+
for _ in range(20):
|
|
183
|
+
aj.write_json_atomic(path, {"writer": i})
|
|
184
|
+
except Exception as exc: # noqa: BLE001 — surfaced below
|
|
185
|
+
errors.append(exc)
|
|
186
|
+
|
|
187
|
+
threads = [threading.Thread(target=writer, args=(i,))
|
|
188
|
+
for i in range(6)]
|
|
189
|
+
for t in threads:
|
|
190
|
+
t.start()
|
|
191
|
+
for t in threads:
|
|
192
|
+
t.join()
|
|
193
|
+
|
|
194
|
+
self.assertEqual(errors, [], f"concurrent writes failed: {errors}")
|
|
195
|
+
doc = json.loads(path.read_text(encoding="utf-8"))
|
|
196
|
+
self.assertIn(doc["writer"], range(6))
|
|
197
|
+
leftovers = list(path.parent.glob("locks.json.tmp*"))
|
|
198
|
+
self.assertEqual(leftovers, [], f"orphaned temps: {leftovers}")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class TestCallSitesUseIt(unittest.TestCase):
|
|
202
|
+
"""The refactor's actual claim: no copy of the shape is left behind.
|
|
203
|
+
|
|
204
|
+
A source scan rather than a behavioural probe, because the four sites sit
|
|
205
|
+
behind a Flask route, an advisory file lock and a scope resolver — and
|
|
206
|
+
because the thing being asserted IS textual: nobody re-derives the publish
|
|
207
|
+
step. `cli/_hook_utils` is exempt on purpose (a PreToolUse subprocess
|
|
208
|
+
imports nothing from `services`).
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
212
|
+
SITES = (
|
|
213
|
+
"services/agent_locks.py",
|
|
214
|
+
"services/access_guard.py",
|
|
215
|
+
"services/shell_output.py",
|
|
216
|
+
"cli/hub_server.py",
|
|
217
|
+
"oracle/services/mobile_api.py",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def test_each_site_imports_the_helper(self):
|
|
221
|
+
for rel in self.SITES:
|
|
222
|
+
with self.subTest(file=rel):
|
|
223
|
+
src = (self.ROOT / rel).read_text(encoding="utf-8")
|
|
224
|
+
self.assertIn("from services.atomic_json import", src)
|
|
225
|
+
|
|
226
|
+
def test_no_site_still_writes_a_shared_temp(self):
|
|
227
|
+
for rel in self.SITES:
|
|
228
|
+
with self.subTest(file=rel):
|
|
229
|
+
src = (self.ROOT / rel).read_text(encoding="utf-8")
|
|
230
|
+
for line in src.splitlines():
|
|
231
|
+
stripped = line.strip()
|
|
232
|
+
if stripped.startswith("#") or '"""' in stripped:
|
|
233
|
+
continue # prose about the old shape is fine
|
|
234
|
+
self.assertNotIn('.name + ".tmp"', stripped)
|
|
235
|
+
self.assertNotIn('.name}.tmp"', stripped)
|
|
236
|
+
self.assertNotIn('"config.json.tmp"', stripped)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
if __name__ == "__main__":
|
|
240
|
+
unittest.main()
|
|
@@ -723,5 +723,103 @@ class TestUnicodePaths(_StoreCase):
|
|
|
723
723
|
self.assertEqual(self.store.list(project_path=project, session_id=session), [])
|
|
724
724
|
|
|
725
725
|
|
|
726
|
+
class TestAtomicWrite(unittest.TestCase):
|
|
727
|
+
"""`_write_json_atomic` — the job store's publish step.
|
|
728
|
+
|
|
729
|
+
Both cases below fail against the pre-2.118.1 version, which used a single
|
|
730
|
+
shared `<name>.tmp` and attempted `os.replace` exactly once. That is what
|
|
731
|
+
crashed a supervisor on Windows CI with WinError 5 and reported "failed
|
|
732
|
+
before running" for a command that was fine.
|
|
733
|
+
"""
|
|
734
|
+
|
|
735
|
+
def setUp(self):
|
|
736
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
737
|
+
self.path = Path(self._tmp.name) / "job.json"
|
|
738
|
+
|
|
739
|
+
def tearDown(self):
|
|
740
|
+
self._tmp.cleanup()
|
|
741
|
+
|
|
742
|
+
def test_temp_name_is_per_process(self):
|
|
743
|
+
"""Two writers must not collide on the temp file itself."""
|
|
744
|
+
seen = []
|
|
745
|
+
real = os.replace
|
|
746
|
+
|
|
747
|
+
def spy(src, dst):
|
|
748
|
+
seen.append(Path(src).name)
|
|
749
|
+
return real(src, dst)
|
|
750
|
+
|
|
751
|
+
with patch.object(so.os, "replace", spy):
|
|
752
|
+
so._write_json_atomic(self.path, {"a": 1})
|
|
753
|
+
self.assertEqual(len(seen), 1)
|
|
754
|
+
self.assertNotEqual(seen[0], "job.json.tmp",
|
|
755
|
+
"shared temp name — concurrent writers will race")
|
|
756
|
+
self.assertTrue(seen[0].startswith("job.json.tmp"))
|
|
757
|
+
self.assertIn(str(os.getpid()), seen[0])
|
|
758
|
+
# …and unique per WRITE, not just per process: this store is written
|
|
759
|
+
# from a threaded server, and threads share a pid.
|
|
760
|
+
seen.clear()
|
|
761
|
+
with patch.object(so.os, "replace", spy):
|
|
762
|
+
so._write_json_atomic(self.path, {"a": 2})
|
|
763
|
+
so._write_json_atomic(self.path, {"a": 3})
|
|
764
|
+
self.assertEqual(len(set(seen)), 2, f"temp name reused: {seen}")
|
|
765
|
+
|
|
766
|
+
def test_transient_permission_error_is_retried(self):
|
|
767
|
+
calls = {"n": 0}
|
|
768
|
+
real = os.replace
|
|
769
|
+
|
|
770
|
+
def flaky(src, dst):
|
|
771
|
+
calls["n"] += 1
|
|
772
|
+
if calls["n"] < 3: # two transient failures, then success
|
|
773
|
+
raise PermissionError(5, "Access is denied")
|
|
774
|
+
return real(src, dst)
|
|
775
|
+
|
|
776
|
+
with patch.object(so.os, "replace", flaky):
|
|
777
|
+
so._write_json_atomic(self.path, {"ok": True})
|
|
778
|
+
self.assertEqual(calls["n"], 3)
|
|
779
|
+
self.assertEqual(json.loads(self.path.read_text(encoding="utf-8")),
|
|
780
|
+
{"ok": True})
|
|
781
|
+
|
|
782
|
+
def test_a_persistent_permission_error_still_raises(self):
|
|
783
|
+
"""Retrying must not swallow a real, non-transient failure."""
|
|
784
|
+
def always(src, dst):
|
|
785
|
+
raise PermissionError(5, "Access is denied")
|
|
786
|
+
|
|
787
|
+
with patch.object(so.os, "replace", always):
|
|
788
|
+
with self.assertRaises(PermissionError):
|
|
789
|
+
so._write_json_atomic(self.path, {"a": 1})
|
|
790
|
+
|
|
791
|
+
def test_no_temp_file_is_left_behind_when_replace_fails(self):
|
|
792
|
+
def always(src, dst):
|
|
793
|
+
raise PermissionError(5, "Access is denied")
|
|
794
|
+
|
|
795
|
+
with patch.object(so.os, "replace", always):
|
|
796
|
+
with self.assertRaises(PermissionError):
|
|
797
|
+
so._write_json_atomic(self.path, {"a": 1})
|
|
798
|
+
leftovers = list(self.path.parent.glob("job.json.tmp*"))
|
|
799
|
+
self.assertEqual(leftovers, [], f"orphaned temp files: {leftovers}")
|
|
800
|
+
|
|
801
|
+
def test_concurrent_writers_all_publish_valid_json(self):
|
|
802
|
+
"""The end-to-end property: N threads hammering one path never leave
|
|
803
|
+
the target truncated or half-written."""
|
|
804
|
+
import threading
|
|
805
|
+
errors = []
|
|
806
|
+
|
|
807
|
+
def writer(i):
|
|
808
|
+
try:
|
|
809
|
+
for _ in range(20):
|
|
810
|
+
so._write_json_atomic(self.path, {"writer": i})
|
|
811
|
+
except Exception as exc: # noqa: BLE001 — surfaced below
|
|
812
|
+
errors.append(exc)
|
|
813
|
+
|
|
814
|
+
threads = [threading.Thread(target=writer, args=(i,)) for i in range(6)]
|
|
815
|
+
for t in threads:
|
|
816
|
+
t.start()
|
|
817
|
+
for t in threads:
|
|
818
|
+
t.join()
|
|
819
|
+
self.assertEqual(errors, [])
|
|
820
|
+
self.assertIn("writer", json.loads(self.path.read_text(encoding="utf-8")))
|
|
821
|
+
self.assertEqual(list(self.path.parent.glob("job.json.tmp*")), [])
|
|
822
|
+
|
|
823
|
+
|
|
726
824
|
if __name__ == "__main__":
|
|
727
825
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|