code-context-control 2.63.2__tar.gz → 2.65.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.63.2 → code_context_control-2.65.0}/PKG-INFO +1 -1
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/c3.py +232 -6
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/commands/parser.py +50 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_access_guard.py +23 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_server.py +94 -8
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/app.js +4 -2
- code_context_control-2.65.0/cli/hub_ui/components/hub_locks.js +248 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/settings_modal.js +0 -6
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/topbar.js +1 -1
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/mcp_server.py +18 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/edit.py +192 -94
- code_context_control-2.65.0/cli/tools/locks.py +132 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/code_context_control.egg-info/PKG-INFO +1 -1
- {code_context_control-2.63.2 → code_context_control-2.65.0}/code_context_control.egg-info/SOURCES.txt +12 -1
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/config.py +3 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/oracle_server.py +141 -11
- code_context_control-2.65.0/oracle/services/chat_store.py +245 -0
- code_context_control-2.65.0/oracle/services/discovery_audit.py +164 -0
- code_context_control-2.65.0/oracle/services/local_session.py +149 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/pyproject.toml +1 -1
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/access_guard.py +218 -10
- code_context_control-2.65.0/services/agent_locks.py +521 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/claude_md.py +97 -16
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/e2e_benchmark.py +40 -6
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/jira_data_center.py +119 -9
- code_context_control-2.65.0/tests/test_access_builtin_optout.py +208 -0
- code_context_control-2.65.0/tests/test_agent_locks.py +470 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_e2e_benchmark.py +46 -2
- code_context_control-2.65.0/tests/test_edit_locking.py +288 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_federated_graph.py +6 -3
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_hook_access_guard.py +66 -0
- code_context_control-2.65.0/tests/test_hub_locks_routes.py +151 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_hub_server_smoke.py +7 -4
- code_context_control-2.65.0/tests/test_ide_workflow_adaptation.py +121 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_jira_client.py +78 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_oracle_apikey_api.py +6 -3
- code_context_control-2.65.0/tests/test_oracle_chat_store.py +176 -0
- code_context_control-2.65.0/tests/test_oracle_discovery_audit.py +194 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_oracle_local_auth.py +31 -11
- code_context_control-2.65.0/tests/test_oracle_session_bootstrap.py +160 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_oracle_ui_bundle.py +4 -2
- code_context_control-2.63.2/cli/hub.html +0 -3889
- code_context_control-2.63.2/oracle/services/chat_store.py +0 -155
- code_context_control-2.63.2/oracle/services/local_session.py +0 -54
- {code_context_control-2.63.2 → code_context_control-2.65.0}/LICENSE +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/README.md +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/__init__.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/_hook_utils.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/commands/__init__.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/commands/common.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/docs.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/edits.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/guide/access.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/guide/bitbucket.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/guide/credentials.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/guide/getting-started.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/guide/index.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/guide/masking.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/guide/oracle.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/guide/shared.css +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/guide/tools.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/guide/workflow.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_artifact.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_auto_snapshot.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_c3_signal.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_c3read.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_dispatch.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_edit_ledger.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_edit_unlock.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_filter.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_ghost_files.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_pretool_enforce.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_prompt_recall.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_read.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_session_stats.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hook_terse_advisor.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/add_project.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/config_editor.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/drill_health.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/drill_panel.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/drill_subprojects.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/drill_tasks.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/drill_views.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/global_search.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/hub_credentials.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/mcp_manager.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/modals.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/project_card.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/project_tree.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/session_drawer.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/sidebar.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/summary_bar.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/task_board.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/components/toasts.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui/state.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/hub_ui.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/mcp_proxy.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/progress.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/server.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/__init__.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/_helpers.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/agent.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/artifacts.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/bitbucket.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/compress.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/credentials.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/delegate.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/edits.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/federate.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/filter.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/impact.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/jira.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/memory.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/project.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/read.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/search.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/session.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/shell.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/status.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/tasks.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/tools/validate.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/api.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/app.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/access.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/bitbucket.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/chat.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/credentials.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/dashboard.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/edits.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/instructions.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/jira.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/memory.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/sessions.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/settings.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/sidebar.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/components/tasks.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/icons.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/pm_shared.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/shared.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui/theme.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui_legacy.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/cli/ui_nano.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/code_context_control.egg-info/dependency_links.txt +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/code_context_control.egg-info/entry_points.txt +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/code_context_control.egg-info/requires.txt +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/code_context_control.egg-info/top_level.txt +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/core/__init__.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/core/config.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/core/ide.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/core/mcp_toml.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/core/web_security.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/__init__.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/mcp_oracle.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/oracle_ui.html +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/__init__.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/activity_reporter.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/api_auth.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/c3_bridge.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/chat_engine.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/cross_memory.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/federated_graph.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/health_checker.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/insight_engine.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/memory_reader.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/memory_writer.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/project_scanner.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/review_agent.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/tool_executor.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/services/tool_registry.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/activity.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/agents.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/app.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/busy.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/chat/conversations.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/chat/input.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/chat/markdown.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/chat/send.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/chat/stream_renderer.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/chat/toolbar.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/core.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/crossgraph.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/header.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/insights.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/projects.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/settings.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/suggestions.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/oracle/ui/theme_tabs.js +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/__init__.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/activity_log.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/agent_base.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/agents.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/artifact_defs.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/artifact_store.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/auto_memory.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/bench/__init__.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/bench/external/__init__.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/bench/external/aider_polyglot.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/bench/external/swe_bench.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/benchmark_dashboard.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/bitbucket_client.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/bitbucket_credentials.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/circuit_breaker.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/compressor.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/context_snapshot.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/conversation_store.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/credential_store.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/doc_index.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/e2e_evaluator.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/e2e_tasks.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/edit_ledger.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/embedding_index.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/error_reporting.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/file_memory.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/git_context.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/hub_service.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/indexer.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/jira_client.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/jira_cloud.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/jira_credentials.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/jira_links.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/mask_activation.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/mask_mirror.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/mask_presets.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/memory.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/memory_consolidator.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/memory_distiller.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/memory_graph.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/memory_grounder.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/memory_queue.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/memory_scorer.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/metrics.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/notifications.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/ollama_bridge.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/ollama_client.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/ollama_credentials.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/output_filter.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/parser.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/project_manager.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/project_runtime.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/protocol.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/proxy_state.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/repo_map.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/retention.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/retrieval_broker.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/router.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/runtime.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/scanner.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/session_benchmark.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/session_manager.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/session_preloader.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/subprojects.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/task_store.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/telemetry.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/text_index.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/time_tracker.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/tool_classifier.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/transcript_index.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/validation_cache.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/vector_store.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/watcher.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/services/win_subprocess.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/setup.cfg +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_access_guard.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_access_guard_meta.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_access_guard_shell_project.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_access_guard_surfaces.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_access_guard_wiring.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_activity_reporter.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_aider_polyglot.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_antigravity_transition.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_artifact_store.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_artifact_tool.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_bitbucket_cli_smoke.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_bitbucket_client.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_bitbucket_config_fallback.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_bitbucket_credentials.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_bitbucket_tool.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_c3_shell.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_circuit_breaker.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_claude_md_merge.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_cli_smoke.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_compressor_large_file.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_credential_store.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_credentials_routes.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_credentials_tool.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_creds_cli_smoke.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_delegate_cascade.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_edit_ledger_hook.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_edit_normalization.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_enforcement_flip.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_filter_backoff.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_ghost_files.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_ghost_generation.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_git_branch_awareness.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_hook_dispatch.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_hook_pretool_enforce.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_hook_prompt_recall.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_hook_smoke.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_hook_state.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_hook_utf8_stdio.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_hub_credentials_route.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_hub_credentials_routes_write.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_hub_inspect_api.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_hub_subproject_links.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_init_scan.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_install_mcp_entrypoint.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_jira_cli_smoke.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_jira_config_fallback.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_jira_credentials.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_jira_links.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_jira_routes.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_jira_tool.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_lazy_store_init.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_mask_guard.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_mask_routes.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_mask_surfaces.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_mcp_host_guard.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_mcp_memory_timeout.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_mcp_server_smoke.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_mcp_toml.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_memory_distiller.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_memory_graph_api.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_memory_queue.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_memory_system.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_no_stale_model_pins.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_notification_dedup.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_notification_discipline.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_ollama_credentials.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_oracle_api_auth.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_oracle_c3_bridge.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_oracle_chat_engine.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_oracle_discovery_api.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_oracle_ollama_bridge.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_oracle_scanner_cache.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_oracle_security_fixes.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_oracle_subproject_awareness.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_output_filter.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_permissions.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_pm_api.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_project_manager.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_project_manager_merge.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_project_tool.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_read_coercion.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_read_edit_parity.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_related_facts.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_repo_map.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_response_boilerplate.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_retention.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_review_digest.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_service_durability.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_session_benchmark.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_session_budget.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_shell_creds.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_shell_robustness.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_subproject_exclusion.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_subproject_federation.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_subprojects.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_swe_bench.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_task_store.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_task_tool.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_time_tracker.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_token_telemetry.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_tool_registry.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_upgrade_and_version.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_validate.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_vault_write_guard.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_version_sync.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_web_security.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tests/test_windows_reliability.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/__init__.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/backend.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/main.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/__init__.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/benchmark_view.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/claudemd_view.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/compress_view.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/index_view.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/init_view.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/mcp_view.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/optimize_view.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/pipe_view.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/projects_view.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/search_view.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/session_view.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/stats.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/screens/ui_view.py +0 -0
- {code_context_control-2.63.2 → code_context_control-2.65.0}/tui/theme.tcss +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-context-control
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.65.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.65.0"
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def _compress_file_cli(compressor, path, mode="smart", **kw):
|
|
@@ -257,7 +257,8 @@ _C3_MCP_ALLOW = [
|
|
|
257
257
|
"mcp__c3__c3_session", "mcp__c3__c3_status", "mcp__c3__c3_filter",
|
|
258
258
|
"mcp__c3__c3_memory", "mcp__c3__c3_validate", "mcp__c3__c3_edit",
|
|
259
259
|
"mcp__c3__c3_agent", "mcp__c3__c3_delegate", "mcp__c3__c3_edits",
|
|
260
|
-
"mcp__c3__c3_impact", "
|
|
260
|
+
"mcp__c3__c3_impact", "mcp__c3__c3_locks", "mcp__c3__c3_shell",
|
|
261
|
+
"mcp__c3__c3_bitbucket",
|
|
261
262
|
"mcp__c3__c3_jira", "mcp__c3__c3_credentials",
|
|
262
263
|
"mcp__c3__c3_project", "mcp__c3__c3_task", "mcp__c3__c3_artifacts",
|
|
263
264
|
]
|
|
@@ -4046,14 +4047,22 @@ def cmd_pipe(args):
|
|
|
4046
4047
|
|
|
4047
4048
|
|
|
4048
4049
|
from services.claude_md import C3_COMPACT_WORKFLOW as _SHARED_C3_COMPACT_WORKFLOW
|
|
4050
|
+
from services.claude_md import VSCODE_SESSION_INIT as _VSCODE_SESSION_INIT
|
|
4051
|
+
from services.claude_md import adapt_workflow_for_ide as _adapt_workflow_for_ide
|
|
4049
4052
|
|
|
4050
4053
|
_C3_COMPACT_WORKFLOW = _SHARED_C3_COMPACT_WORKFLOW
|
|
4051
4054
|
|
|
4052
4055
|
_CLAUDE_MD_CONTENT = _C3_COMPACT_WORKFLOW
|
|
4053
4056
|
|
|
4054
|
-
|
|
4057
|
+
# Copilot and Codex run without PreToolUse hooks and without /clear, so both
|
|
4058
|
+
# docs get the hookless restatement of the mandate (see PROFILES in core/ide.py).
|
|
4059
|
+
_C3_HOOKLESS_WORKFLOW = _adapt_workflow_for_ide(
|
|
4060
|
+
_C3_COMPACT_WORKFLOW, supports_hooks=False, supports_clear=False
|
|
4061
|
+
)
|
|
4062
|
+
|
|
4063
|
+
_COPILOT_INSTRUCTIONS_CONTENT = _VSCODE_SESSION_INIT + "\n\n" + _C3_HOOKLESS_WORKFLOW
|
|
4055
4064
|
|
|
4056
|
-
_AGENTS_MD_CONTENT =
|
|
4065
|
+
_AGENTS_MD_CONTENT = _C3_HOOKLESS_WORKFLOW + """
|
|
4057
4066
|
|
|
4058
4067
|
## IDE Configuration (Codex)
|
|
4059
4068
|
This project uses project-scoped MCP servers. Ensure your `.codex/config.toml` includes:
|
|
@@ -5200,6 +5209,7 @@ def cmd_install_mcp(args):
|
|
|
5200
5209
|
"mcp__c3__c3_edit",
|
|
5201
5210
|
"mcp__c3__c3_edits",
|
|
5202
5211
|
"mcp__c3__c3_impact",
|
|
5212
|
+
"mcp__c3__c3_locks",
|
|
5203
5213
|
"mcp__c3__c3_status",
|
|
5204
5214
|
"mcp__c3__c3_delegate",
|
|
5205
5215
|
"mcp__c3__c3_session",
|
|
@@ -5915,12 +5925,90 @@ def _creds_cmd_import(args, project_path: str) -> None:
|
|
|
5915
5925
|
"(use --overwrite to replace)")
|
|
5916
5926
|
|
|
5917
5927
|
|
|
5928
|
+
def cmd_locks(args):
|
|
5929
|
+
"""Agent Locks — human surface. force-release lives here, never in c3_locks."""
|
|
5930
|
+
from services import agent_locks as al
|
|
5931
|
+
|
|
5932
|
+
sub = getattr(args, "locks_cmd", None) or "list"
|
|
5933
|
+
project_path = getattr(args, "project_path", ".") or "."
|
|
5934
|
+
store = al.store_for(project_path)
|
|
5935
|
+
cfg = al.config(project_path)
|
|
5936
|
+
|
|
5937
|
+
if sub == "list":
|
|
5938
|
+
snap = store.snapshot()
|
|
5939
|
+
print(f"{snap['count']} active lease(s) — mode={cfg['mode']}, "
|
|
5940
|
+
f"enabled={cfg['enabled']}, fencing={snap['fencing']}")
|
|
5941
|
+
if not snap["locks"]:
|
|
5942
|
+
return
|
|
5943
|
+
print(f" {'file':<44} {'holder':<24} {'left':<8} intent")
|
|
5944
|
+
for row in snap["locks"]:
|
|
5945
|
+
left = int(max(0, row.get("expires_in_s", 0)))
|
|
5946
|
+
print(f" {row['relpath']:<44} {row['agent_id']:<24} "
|
|
5947
|
+
f"{left//60}m{left % 60:02d}s {row.get('intent', '')}")
|
|
5948
|
+
return
|
|
5949
|
+
|
|
5950
|
+
if sub == "release":
|
|
5951
|
+
res = store.release(session_id=args.session)
|
|
5952
|
+
print(f"Released {res['count']} lease(s)"
|
|
5953
|
+
+ (f": {', '.join(res['released'])}" if res["released"] else ""))
|
|
5954
|
+
return
|
|
5955
|
+
|
|
5956
|
+
if sub == "force-release":
|
|
5957
|
+
res = store.force_release(args.file, by="cli", note=args.note)
|
|
5958
|
+
if not res.get("forced"):
|
|
5959
|
+
print(f"[error] {res.get('error')}: {res.get('reason', '')}")
|
|
5960
|
+
return
|
|
5961
|
+
_locks_audit(args.file, res, project_path, args.note)
|
|
5962
|
+
if res["was_locked"]:
|
|
5963
|
+
print(f"[OK] Broke the lease on {res['relpath']} "
|
|
5964
|
+
f"(was held by {res['previous_owner']}).")
|
|
5965
|
+
print(" The fencing token moved, so the old holder is stale "
|
|
5966
|
+
"even if it thinks it still holds it.")
|
|
5967
|
+
else:
|
|
5968
|
+
print(f"[=] {res['relpath']} was not locked; fencing bumped anyway.")
|
|
5969
|
+
return
|
|
5970
|
+
|
|
5971
|
+
if sub == "sweep":
|
|
5972
|
+
res = store.sweep()
|
|
5973
|
+
print(f"Swept {res['count']} expired lease(s)"
|
|
5974
|
+
+ (f": {', '.join(res['expired'])}" if res["expired"] else ""))
|
|
5975
|
+
return
|
|
5976
|
+
|
|
5977
|
+
print("Usage: c3 locks {list,release,force-release,sweep}")
|
|
5978
|
+
|
|
5979
|
+
|
|
5980
|
+
def _locks_audit(target: str, res: dict, project_path: str, note: str) -> None:
|
|
5981
|
+
"""Ledger + activity log for a human lease override. Failure-safe."""
|
|
5982
|
+
try:
|
|
5983
|
+
from services.activity_log import ActivityLog
|
|
5984
|
+
ActivityLog(project_path).log("lock_force_release", {
|
|
5985
|
+
"kind": "locks", "relpath": res.get("relpath"),
|
|
5986
|
+
"previous_owner": res.get("previous_owner"), "via": "cli",
|
|
5987
|
+
})
|
|
5988
|
+
except Exception:
|
|
5989
|
+
pass
|
|
5990
|
+
try:
|
|
5991
|
+
from services.edit_ledger import EditLedger
|
|
5992
|
+
EditLedger(project_path).log_edit(
|
|
5993
|
+
file=f"lock://{res.get('relpath', target)}",
|
|
5994
|
+
change_type="lock_force_release",
|
|
5995
|
+
summary=f"force-released lease on {res.get('relpath', target)}"
|
|
5996
|
+
+ (f" — {note}" if note else ""),
|
|
5997
|
+
tags=["locks", "force_release"],
|
|
5998
|
+
detail={"kind": "locks", "previous_owner": res.get("previous_owner"),
|
|
5999
|
+
"was_locked": res.get("was_locked"), "note": note},
|
|
6000
|
+
)
|
|
6001
|
+
except Exception:
|
|
6002
|
+
pass
|
|
6003
|
+
|
|
6004
|
+
|
|
5918
6005
|
def cmd_access(args):
|
|
5919
6006
|
"""Access Guard rule management — human-only mutation surface (spec §1)."""
|
|
5920
6007
|
sub = getattr(args, "access_cmd", None)
|
|
5921
6008
|
if not sub:
|
|
5922
|
-
print("Usage: c3 access {list,add,remove,check,mask} [args]")
|
|
6009
|
+
print("Usage: c3 access {list,add,remove,check,mask,builtin} [args]")
|
|
5923
6010
|
print(" c3 access mask {add,rm,status,activate,preview} [args]")
|
|
6011
|
+
print(" c3 access builtin {disable,enable} <glob>")
|
|
5924
6012
|
return
|
|
5925
6013
|
|
|
5926
6014
|
project_path = getattr(args, "project_path", ".") or "."
|
|
@@ -5935,6 +6023,8 @@ def cmd_access(args):
|
|
|
5935
6023
|
_access_cmd_check(args, project_path)
|
|
5936
6024
|
elif sub == "mask":
|
|
5937
6025
|
_access_cmd_mask(args, project_path)
|
|
6026
|
+
elif sub == "builtin":
|
|
6027
|
+
_access_cmd_builtin(args, project_path)
|
|
5938
6028
|
else:
|
|
5939
6029
|
print(f"Unknown access subcommand: {sub}")
|
|
5940
6030
|
|
|
@@ -5989,6 +6079,25 @@ def _access_cmd_list(args, project_path: str) -> None:
|
|
|
5989
6079
|
if sec.get("corrupt"):
|
|
5990
6080
|
print(" [warn] access section invalid — scope fails closed "
|
|
5991
6081
|
"(deny-all); fix config.json 'access' by hand")
|
|
6082
|
+
if scope == "builtin":
|
|
6083
|
+
off = sec.get("disabled") or []
|
|
6084
|
+
if off:
|
|
6085
|
+
print(" DISABLED by you (not enforced):")
|
|
6086
|
+
for glob in off:
|
|
6087
|
+
print(f" {'off':<10} {glob}")
|
|
6088
|
+
print(" re-enable: c3 access builtin enable <glob>")
|
|
6089
|
+
can = [g for g in (sec.get("disableable") or [])
|
|
6090
|
+
if access_guard._norm_builtin(g) not in set(off)]
|
|
6091
|
+
if can:
|
|
6092
|
+
print(" disableable (currently on): "
|
|
6093
|
+
+ ", ".join(can))
|
|
6094
|
+
print(" disable: c3 access builtin disable <glob>")
|
|
6095
|
+
if sec.get("absolute"):
|
|
6096
|
+
print(" absolute (credential vault, cannot be disabled): "
|
|
6097
|
+
+ ", ".join(sec["absolute"]))
|
|
6098
|
+
print(" spelling rules (deny by how a path is written, no glob):")
|
|
6099
|
+
for name, why in access_guard.SYNTHETIC_RULES:
|
|
6100
|
+
print(f" {name:<18} {why}")
|
|
5992
6101
|
print()
|
|
5993
6102
|
from services import mask_activation
|
|
5994
6103
|
summary = mask_activation.summary_line(project_path)
|
|
@@ -5998,6 +6107,58 @@ def _access_cmd_list(args, project_path: str) -> None:
|
|
|
5998
6107
|
print(access_guard.COVERAGE_MATRIX)
|
|
5999
6108
|
|
|
6000
6109
|
|
|
6110
|
+
def _access_cmd_builtin(args, project_path: str) -> None:
|
|
6111
|
+
"""Switch a Tier-1 builtin off or back on. Human-only; always global scope.
|
|
6112
|
+
|
|
6113
|
+
Disabling asks for typed confirmation unless --yes: these guards are what
|
|
6114
|
+
stop an agent rewriting its own hooks, settings or git history, and the
|
|
6115
|
+
consequence deserves a beat of friction.
|
|
6116
|
+
"""
|
|
6117
|
+
from services import access_guard
|
|
6118
|
+
|
|
6119
|
+
action = getattr(args, "builtin_cmd", None)
|
|
6120
|
+
glob = getattr(args, "glob", None)
|
|
6121
|
+
if action not in ("disable", "enable") or not glob:
|
|
6122
|
+
print("Usage: c3 access builtin {disable,enable} <glob>")
|
|
6123
|
+
print(" disableable: "
|
|
6124
|
+
+ ", ".join(access_guard.DISABLEABLE_BUILTINS))
|
|
6125
|
+
return
|
|
6126
|
+
|
|
6127
|
+
disabling = action == "disable"
|
|
6128
|
+
if disabling and not getattr(args, "yes", False):
|
|
6129
|
+
print(f"About to STOP enforcing the builtin guard on: {glob}")
|
|
6130
|
+
print(" While off, any agent with C3 tools can write those paths.")
|
|
6131
|
+
if glob.endswith("settings*.json") or ".claude" in glob:
|
|
6132
|
+
print(" This is the file that configures the agent itself — hooks,")
|
|
6133
|
+
print(" permissions, model. An agent could rewrite its own rules.")
|
|
6134
|
+
reply = input("Type the glob again to confirm: ").strip()
|
|
6135
|
+
if reply != glob:
|
|
6136
|
+
print("[abort] Not confirmed; nothing changed.")
|
|
6137
|
+
return
|
|
6138
|
+
|
|
6139
|
+
try:
|
|
6140
|
+
result = access_guard.set_builtin_disabled(glob, disabling)
|
|
6141
|
+
except ValueError as exc:
|
|
6142
|
+
print(f"[error] {exc}")
|
|
6143
|
+
return
|
|
6144
|
+
|
|
6145
|
+
_access_audit(f"builtin_{action}", result["glob"], "builtin", "global",
|
|
6146
|
+
project_path)
|
|
6147
|
+
if not result["changed"]:
|
|
6148
|
+
state = "already disabled" if disabling else "already enforced"
|
|
6149
|
+
print(f"[=] {result['glob']} — {state}")
|
|
6150
|
+
return
|
|
6151
|
+
if disabling:
|
|
6152
|
+
print(f"[OK] Builtin '{result['glob']}' DISABLED (global scope).")
|
|
6153
|
+
print(" Re-enable with: c3 access builtin enable "
|
|
6154
|
+
f"{result['glob']}")
|
|
6155
|
+
else:
|
|
6156
|
+
print(f"[OK] Builtin '{result['glob']}' re-enforced.")
|
|
6157
|
+
if not result["attested"]:
|
|
6158
|
+
print(" [warn] keyring attestation could not be cleared; the "
|
|
6159
|
+
"builtin is enforced anyway (config entry removed).")
|
|
6160
|
+
|
|
6161
|
+
|
|
6001
6162
|
def _access_cmd_add(args, project_path: str) -> None:
|
|
6002
6163
|
from services import access_guard
|
|
6003
6164
|
|
|
@@ -6404,6 +6565,66 @@ def _jira_cmd_set_default(args, project_path: str) -> None:
|
|
|
6404
6565
|
print(f"[OK] Default Jira project: {args.project}")
|
|
6405
6566
|
|
|
6406
6567
|
|
|
6568
|
+
def _oracle_open(args):
|
|
6569
|
+
"""Mint a single-use sign-in URL for a running Oracle dashboard (#31).
|
|
6570
|
+
|
|
6571
|
+
Plain ``GET /`` no longer issues the session cookie, so a dashboard opened
|
|
6572
|
+
by hand is read-only until a bootstrap code is redeemed. Reading the
|
|
6573
|
+
owner-only key file is what proves we are the same OS user as the server.
|
|
6574
|
+
"""
|
|
6575
|
+
import json as _json
|
|
6576
|
+
import urllib.error
|
|
6577
|
+
import urllib.request
|
|
6578
|
+
import webbrowser
|
|
6579
|
+
|
|
6580
|
+
from oracle.config import ORACLE_DIR, load_config
|
|
6581
|
+
from oracle.services import local_session
|
|
6582
|
+
|
|
6583
|
+
cfg = load_config()
|
|
6584
|
+
host = cfg.get("bind_host", "127.0.0.1")
|
|
6585
|
+
disp_host = "127.0.0.1" if host in ("0.0.0.0", "") else host
|
|
6586
|
+
port = getattr(args, "port", None) or cfg.get("port", 3331)
|
|
6587
|
+
base = f"http://{disp_host}:{port}"
|
|
6588
|
+
|
|
6589
|
+
key = local_session.read_bootstrap_key(ORACLE_DIR)
|
|
6590
|
+
if not key:
|
|
6591
|
+
print(f"[oracle:open] no bootstrap key at "
|
|
6592
|
+
f"{ORACLE_DIR / local_session.BOOTSTRAP_KEY_FILENAME}")
|
|
6593
|
+
print(" The key is written when the server starts. Is Oracle running?")
|
|
6594
|
+
print(" Start it with: c3 oracle serve")
|
|
6595
|
+
return
|
|
6596
|
+
|
|
6597
|
+
req = urllib.request.Request(
|
|
6598
|
+
f"{base}/api/session/bootstrap",
|
|
6599
|
+
data=_json.dumps({"key": key}).encode("utf-8"),
|
|
6600
|
+
headers={"Content-Type": "application/json"},
|
|
6601
|
+
method="POST",
|
|
6602
|
+
)
|
|
6603
|
+
try:
|
|
6604
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
6605
|
+
payload = _json.loads(resp.read().decode("utf-8"))
|
|
6606
|
+
except urllib.error.HTTPError as e:
|
|
6607
|
+
detail = e.read().decode("utf-8", "replace")[:200]
|
|
6608
|
+
print(f"[oracle:open] server refused the request ({e.code}): {detail}")
|
|
6609
|
+
if e.code == 401:
|
|
6610
|
+
print(" The key on disk is stale — restart the server to rewrite it.")
|
|
6611
|
+
return
|
|
6612
|
+
except OSError as e:
|
|
6613
|
+
print(f"[oracle:open] could not reach {base}: {e}")
|
|
6614
|
+
print(" Start it with: c3 oracle serve")
|
|
6615
|
+
return
|
|
6616
|
+
|
|
6617
|
+
url = payload.get("url", "")
|
|
6618
|
+
if not url:
|
|
6619
|
+
print("[oracle:open] server returned no URL")
|
|
6620
|
+
return
|
|
6621
|
+
ttl = payload.get("expires_in", local_session.CODE_TTL_SECONDS)
|
|
6622
|
+
print(f"[oracle:open] single-use sign-in link (valid {ttl}s, one use):")
|
|
6623
|
+
print(f" {url}")
|
|
6624
|
+
if not getattr(args, "no_browser", False):
|
|
6625
|
+
webbrowser.open(url)
|
|
6626
|
+
|
|
6627
|
+
|
|
6407
6628
|
def cmd_oracle(args):
|
|
6408
6629
|
"""Oracle dashboard server + Discovery API key management."""
|
|
6409
6630
|
sub = getattr(args, "oracle_cmd", None)
|
|
@@ -6414,8 +6635,12 @@ def cmd_oracle(args):
|
|
|
6414
6635
|
run_oracle(port=getattr(args, "port", None),
|
|
6415
6636
|
open_browser=not getattr(args, "no_browser", False))
|
|
6416
6637
|
return
|
|
6638
|
+
if sub == "open":
|
|
6639
|
+
_oracle_open(args)
|
|
6640
|
+
return
|
|
6417
6641
|
if sub != "api":
|
|
6418
|
-
print("Usage: c3 oracle {serve,api} — serve: launch the dashboard; "
|
|
6642
|
+
print("Usage: c3 oracle {serve,open,api} — serve: launch the dashboard; "
|
|
6643
|
+
"open: sign in to a running dashboard; "
|
|
6419
6644
|
"api {info,key,rotate,clear}: manage the Discovery key")
|
|
6420
6645
|
return
|
|
6421
6646
|
|
|
@@ -7507,6 +7732,7 @@ def main():
|
|
|
7507
7732
|
"jira": cmd_jira,
|
|
7508
7733
|
"creds": cmd_creds,
|
|
7509
7734
|
"access": cmd_access,
|
|
7735
|
+
"locks": cmd_locks,
|
|
7510
7736
|
"oracle": cmd_oracle,
|
|
7511
7737
|
"upgrade": cmd_upgrade,
|
|
7512
7738
|
}
|
|
@@ -447,6 +447,30 @@ def build_parser(version: str, parse_cli_ide_arg):
|
|
|
447
447
|
cr_import.add_argument("--overwrite", action="store_true", help="Replace entries already registered in the target scope")
|
|
448
448
|
cr_import.add_argument("--path", dest="project_path", default=".", help="Project directory (default: current)")
|
|
449
449
|
|
|
450
|
+
# ── Agent Locks (docs/agent-locks.md) ───────────────────────────────
|
|
451
|
+
# force-release is human-only: it bumps the fencing counter so a holder
|
|
452
|
+
# that comes back is stale by construction. Agents get c3_locks instead,
|
|
453
|
+
# which deliberately has no force action.
|
|
454
|
+
p_locks = subparsers.add_parser(
|
|
455
|
+
"locks", help="Agent leases — see who holds which file, release a stuck one")
|
|
456
|
+
locks_subs = p_locks.add_subparsers(dest="locks_cmd")
|
|
457
|
+
|
|
458
|
+
lk_list = locks_subs.add_parser("list", help="Show active leases")
|
|
459
|
+
lk_list.add_argument("--path", dest="project_path", default=".")
|
|
460
|
+
|
|
461
|
+
lk_rel = locks_subs.add_parser("release", help="Release leases held by a session")
|
|
462
|
+
lk_rel.add_argument("--session", required=True, help="session_id to release")
|
|
463
|
+
lk_rel.add_argument("--path", dest="project_path", default=".")
|
|
464
|
+
|
|
465
|
+
lk_force = locks_subs.add_parser(
|
|
466
|
+
"force-release", help="Break one lease regardless of holder (audited)")
|
|
467
|
+
lk_force.add_argument("file", help="Project-relative path to unlock")
|
|
468
|
+
lk_force.add_argument("--note", default="", help="Why — recorded in the ledger")
|
|
469
|
+
lk_force.add_argument("--path", dest="project_path", default=".")
|
|
470
|
+
|
|
471
|
+
lk_sweep = locks_subs.add_parser("sweep", help="Drop expired leases now")
|
|
472
|
+
lk_sweep.add_argument("--path", dest="project_path", default=".")
|
|
473
|
+
|
|
450
474
|
# ── Access Guard (v2.62.0) ──────────────────────────────────────────
|
|
451
475
|
# Human-only mutation surface (frozen spec docs/access-guard.md §1):
|
|
452
476
|
# rule changes happen here or in the UI tab, never via an agent tool.
|
|
@@ -483,6 +507,24 @@ def build_parser(version: str, parse_cli_ide_arg):
|
|
|
483
507
|
help="Operation to evaluate (default: read)")
|
|
484
508
|
ac_check.add_argument("--path", dest="project_path", default=".", help="Project directory (default: current)")
|
|
485
509
|
|
|
510
|
+
# ── Builtin opt-out (two-key) ───────────────────────────────────────
|
|
511
|
+
# Builtins are on by default. Switching one off needs a config entry AND
|
|
512
|
+
# a keyring attestation, so an agent that writes config.json alone still
|
|
513
|
+
# cannot loosen the guard. Global scope only — project scopes may only
|
|
514
|
+
# ever tighten. The credential-vault builtins are never disableable.
|
|
515
|
+
ac_builtin = access_subs.add_parser(
|
|
516
|
+
"builtin", help="Disable or re-enable a built-in guard (global, needs confirmation)")
|
|
517
|
+
builtin_subs = ac_builtin.add_subparsers(dest="builtin_cmd")
|
|
518
|
+
for _name, _help in (("disable", "Stop enforcing a built-in guard"),
|
|
519
|
+
("enable", "Re-enforce a built-in guard")):
|
|
520
|
+
_p = builtin_subs.add_parser(_name, help=_help)
|
|
521
|
+
_p.add_argument("glob", help="One of: **/.env*, **/.c3/**, **/.claude/settings*.json, **/.git/**")
|
|
522
|
+
_p.add_argument("--path", dest="project_path", default=".",
|
|
523
|
+
help="Project directory used for the audit log (default: current)")
|
|
524
|
+
if _name == "disable":
|
|
525
|
+
_p.add_argument("--yes", action="store_true",
|
|
526
|
+
help="Skip the typed confirmation (scripts/CI)")
|
|
527
|
+
|
|
486
528
|
# ── Mask Guard (v2.63.0, docs/mask-guard.md) ────────────────────────
|
|
487
529
|
# Masking exposes a path but transforms what the agent sees. Rules are
|
|
488
530
|
# human-only, like the rest of Access Guard.
|
|
@@ -540,6 +582,14 @@ def build_parser(version: str, parse_cli_ide_arg):
|
|
|
540
582
|
help="Server port (default: config 'port', 3331)")
|
|
541
583
|
or_serve.add_argument("--no-browser", action="store_true",
|
|
542
584
|
help="Don't open the browser")
|
|
585
|
+
or_open = or_subs.add_parser(
|
|
586
|
+
"open",
|
|
587
|
+
help="Sign in to a running dashboard via a single-use URL",
|
|
588
|
+
)
|
|
589
|
+
or_open.add_argument("--port", type=int, default=None,
|
|
590
|
+
help="Server port (default: config 'port', 3331)")
|
|
591
|
+
or_open.add_argument("--no-browser", action="store_true",
|
|
592
|
+
help="Print the URL without opening a browser")
|
|
543
593
|
or_api = or_subs.add_parser(
|
|
544
594
|
"api",
|
|
545
595
|
help="Show connection info / manage the Discovery API key",
|
|
@@ -31,6 +31,23 @@ _SHELL_TOOLS = {"Bash"} # normalize_tool_name folds run_shell_command in
|
|
|
31
31
|
_TOKEN_SPLIT = re.compile(r"\s+")
|
|
32
32
|
_MAX_TOKENS = 200
|
|
33
33
|
|
|
34
|
+
# A colon in a shell token overwhelmingly means "URL scheme" or "IPv6", not
|
|
35
|
+
# "NTFS alternate data stream". The ADS check stays strict for real path
|
|
36
|
+
# arguments (_WRITE_TOOLS/_READ_TOOLS); here it only produced false denies on
|
|
37
|
+
# commit messages, curl, and gh (#50).
|
|
38
|
+
_SCHEME_RE = re.compile(r"^[a-z][a-z0-9+.\-]*://", re.IGNORECASE)
|
|
39
|
+
# IPv6 literal or CIDR: >=2 colons, hex groups only, optional [] and /prefix.
|
|
40
|
+
# 'C:/x/f.txt:stream' has one colon and non-hex parts, so it never matches.
|
|
41
|
+
_IPV6_RE = re.compile(
|
|
42
|
+
r"^\[?[0-9a-f]{0,4}(?::[0-9a-f]{0,4}){2,}\]?(?:/\d{1,3})?$",
|
|
43
|
+
re.IGNORECASE,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _is_network_token(tok: str) -> bool:
|
|
48
|
+
"""True for URLs and IPv6 literals/CIDRs — never for an ADS spelling."""
|
|
49
|
+
return bool(_SCHEME_RE.match(tok) or _IPV6_RE.match(tok))
|
|
50
|
+
|
|
34
51
|
|
|
35
52
|
def _deny(reason: str) -> dict:
|
|
36
53
|
return {
|
|
@@ -57,6 +74,10 @@ def _scan_shell(cmd: str, base: str):
|
|
|
57
74
|
Confident = the token resolves to an EXISTING path that a ``deny`` rule
|
|
58
75
|
covers (existence-gating keeps regex/pattern arguments like '\\.env'
|
|
59
76
|
from false-denying), or the evaluator flags the spelling itself.
|
|
77
|
+
|
|
78
|
+
URLs and IPv6 literals are skipped outright: they trip the ADS spelling
|
|
79
|
+
check, which is exempt from existence-gating, so a token naming nothing
|
|
80
|
+
on disk would otherwise hard-deny (#50).
|
|
60
81
|
"""
|
|
61
82
|
for raw in _TOKEN_SPLIT.split(cmd)[:_MAX_TOKENS]:
|
|
62
83
|
tok = raw.strip("\"'`;,()")
|
|
@@ -64,6 +85,8 @@ def _scan_shell(cmd: str, base: str):
|
|
|
64
85
|
continue
|
|
65
86
|
if "/" not in tok and "\\" not in tok and not tok.startswith("."):
|
|
66
87
|
continue
|
|
88
|
+
if _is_network_token(tok):
|
|
89
|
+
continue
|
|
67
90
|
if re.match(r"^/[a-z]/", tok): # MSYS /c/foo → C:/foo
|
|
68
91
|
tok = f"{tok[1]}:{tok[2:]}"
|
|
69
92
|
denial = ag.check(tok, "read", base)
|
|
@@ -335,6 +335,7 @@ _HUB_JS_FILES = [
|
|
|
335
335
|
"hub_ui/components/drill_panel.js",
|
|
336
336
|
"hub_ui/components/drill_views.js",
|
|
337
337
|
"hub_ui/components/hub_credentials.js",
|
|
338
|
+
"hub_ui/components/hub_locks.js",
|
|
338
339
|
"hub_ui/components/drill_subprojects.js",
|
|
339
340
|
"hub_ui/components/drill_health.js",
|
|
340
341
|
"hub_ui/components/drill_tasks.js",
|
|
@@ -379,12 +380,6 @@ def index():
|
|
|
379
380
|
return Response(_hub_html_cache, mimetype="text/html")
|
|
380
381
|
|
|
381
382
|
|
|
382
|
-
@app.route("/legacy")
|
|
383
|
-
def index_legacy():
|
|
384
|
-
"""Frozen pre-2.44 hub UI — escape hatch for one release, then removed."""
|
|
385
|
-
return send_from_directory(str(Path(__file__).parent), "hub.html")
|
|
386
|
-
|
|
387
|
-
|
|
388
383
|
@app.route("/guide/")
|
|
389
384
|
@app.route("/guide/<path:filename>")
|
|
390
385
|
def serve_guide(filename="index.html"):
|
|
@@ -439,8 +434,9 @@ def api_hub_config_set():
|
|
|
439
434
|
cfg["projects_view"] = projects_view
|
|
440
435
|
if "main_view" in data:
|
|
441
436
|
main_view = str(data["main_view"]).strip().lower()
|
|
442
|
-
if main_view not in {"projects", "board", "creds"}:
|
|
443
|
-
return jsonify({"error": "main_view must be 'projects', 'board'
|
|
437
|
+
if main_view not in {"projects", "board", "creds", "locks"}:
|
|
438
|
+
return jsonify({"error": "main_view must be 'projects', 'board', "
|
|
439
|
+
"'creds' or 'locks'"}), 400
|
|
444
440
|
cfg["main_view"] = main_view
|
|
445
441
|
if "oracle_url" in data:
|
|
446
442
|
cfg["oracle_url"] = str(data["oracle_url"]).strip()
|
|
@@ -2045,6 +2041,96 @@ def api_hub_credentials_overview():
|
|
|
2045
2041
|
return jsonify({"global": {"entries": global_out}, "projects": projects_out})
|
|
2046
2042
|
|
|
2047
2043
|
|
|
2044
|
+
# ── Agent Locks (hub) ────────────────────────────────────────────────────────
|
|
2045
|
+
# Read + one human override. force-release is here and in `c3 locks`, never in
|
|
2046
|
+
# the c3_locks agent tool: it bumps the fencing counter so a holder that comes
|
|
2047
|
+
# back is stale by construction, which is a decision for a person.
|
|
2048
|
+
|
|
2049
|
+
|
|
2050
|
+
@app.route("/api/hub/locks/overview", methods=["GET"])
|
|
2051
|
+
def api_hub_locks_overview():
|
|
2052
|
+
"""Cross-project lease snapshot for the Locks tab.
|
|
2053
|
+
|
|
2054
|
+
Per-row isolation like /api/hub/credentials/overview: one unreadable
|
|
2055
|
+
project must not blank the page. A project we cannot read reports
|
|
2056
|
+
``error``/``initialized`` rather than an empty lease list — "0 leases"
|
|
2057
|
+
would claim the repo is clear when we simply do not know.
|
|
2058
|
+
"""
|
|
2059
|
+
from services import agent_locks as al
|
|
2060
|
+
|
|
2061
|
+
rows, total = [], 0
|
|
2062
|
+
for p in _pm().list_projects():
|
|
2063
|
+
ppath = str(p.get("path") or "")
|
|
2064
|
+
row = {"name": p.get("name") or "", "path": ppath, "initialized": True,
|
|
2065
|
+
"error": None, "enabled": True, "mode": "advisory",
|
|
2066
|
+
"locks": [], "count": 0, "fencing": 0}
|
|
2067
|
+
try:
|
|
2068
|
+
if not (Path(ppath) / ".c3").is_dir():
|
|
2069
|
+
row["initialized"] = False
|
|
2070
|
+
else:
|
|
2071
|
+
cfg = al.config(ppath)
|
|
2072
|
+
row["enabled"] = cfg["enabled"]
|
|
2073
|
+
row["mode"] = cfg["mode"]
|
|
2074
|
+
snap = al.store_for(ppath).snapshot()
|
|
2075
|
+
row["locks"] = snap["locks"]
|
|
2076
|
+
row["count"] = snap["count"]
|
|
2077
|
+
row["fencing"] = snap["fencing"]
|
|
2078
|
+
total += snap["count"]
|
|
2079
|
+
except Exception as e:
|
|
2080
|
+
row["error"] = str(e)
|
|
2081
|
+
rows.append(row)
|
|
2082
|
+
return jsonify({"projects": rows, "total": total,
|
|
2083
|
+
# Surfaced so the UI can state its own limits instead of
|
|
2084
|
+
# implying a lease covers everything (spec §9).
|
|
2085
|
+
"coverage_note": (
|
|
2086
|
+
"Leases gate C3 tool surfaces only. A raw shell "
|
|
2087
|
+
"redirect, a non-Claude agent, or a human in an editor "
|
|
2088
|
+
"is not covered.")})
|
|
2089
|
+
|
|
2090
|
+
|
|
2091
|
+
@app.route("/api/projects/locks/force-release", methods=["POST"])
|
|
2092
|
+
def api_projects_locks_force_release():
|
|
2093
|
+
"""Break one lease regardless of holder. Human-only, audited on the target."""
|
|
2094
|
+
from services import agent_locks as al
|
|
2095
|
+
|
|
2096
|
+
data = request.get_json(force=True) or {}
|
|
2097
|
+
path = (data.get("path") or "").strip()
|
|
2098
|
+
relpath = (data.get("relpath") or "").strip()
|
|
2099
|
+
note = (data.get("note") or "").strip()
|
|
2100
|
+
if not path or not relpath:
|
|
2101
|
+
return jsonify({"error": "path and relpath are required"}), 400
|
|
2102
|
+
try:
|
|
2103
|
+
resolved = _resolve_project_path(path)
|
|
2104
|
+
except ValueError as e:
|
|
2105
|
+
return jsonify({"error": str(e)}), 404
|
|
2106
|
+
|
|
2107
|
+
res = al.store_for(resolved).force_release(relpath, by="hub", note=note)
|
|
2108
|
+
if not res.get("forced"):
|
|
2109
|
+
return jsonify({"error": res.get("error", "force-release failed"),
|
|
2110
|
+
"reason": res.get("reason", "")}), 400
|
|
2111
|
+
|
|
2112
|
+
try:
|
|
2113
|
+
from services.activity_log import ActivityLog
|
|
2114
|
+
ActivityLog(resolved).log("lock_force_release", {
|
|
2115
|
+
"kind": "locks", "relpath": res.get("relpath"),
|
|
2116
|
+
"previous_owner": res.get("previous_owner"), "via": "hub"})
|
|
2117
|
+
except Exception:
|
|
2118
|
+
pass
|
|
2119
|
+
try:
|
|
2120
|
+
from services.edit_ledger import EditLedger
|
|
2121
|
+
EditLedger(resolved).log_edit(
|
|
2122
|
+
file=f"lock://{res.get('relpath', relpath)}",
|
|
2123
|
+
change_type="lock_force_release",
|
|
2124
|
+
summary=f"force-released lease on {res.get('relpath', relpath)}"
|
|
2125
|
+
+ (f" — {note}" if note else ""),
|
|
2126
|
+
tags=["locks", "force_release"],
|
|
2127
|
+
detail={"kind": "locks", "previous_owner": res.get("previous_owner"),
|
|
2128
|
+
"was_locked": res.get("was_locked"), "note": note})
|
|
2129
|
+
except Exception:
|
|
2130
|
+
pass
|
|
2131
|
+
return jsonify(res)
|
|
2132
|
+
|
|
2133
|
+
|
|
2048
2134
|
@app.route("/api/projects/config", methods=["PUT"])
|
|
2049
2135
|
def api_projects_config_put():
|
|
2050
2136
|
"""Whitelisted section write: deep-merge, atomic replace, audited on the target."""
|
|
@@ -17,7 +17,7 @@ function App() {
|
|
|
17
17
|
const [version, setVersion] = useState('');
|
|
18
18
|
const [projects, setProjects] = useState([]);
|
|
19
19
|
const [loaded, setLoaded] = useState(false);
|
|
20
|
-
const [mainView, setMainView] = useState('projects'); // projects | board | creds
|
|
20
|
+
const [mainView, setMainView] = useState('projects'); // projects | board | creds | locks
|
|
21
21
|
const [filter, setFilter] = useState('all'); // all | active | idle | tag:<x>
|
|
22
22
|
const [search, setSearch] = useState('');
|
|
23
23
|
const [view, setView] = useState('list'); // list | grid
|
|
@@ -44,7 +44,7 @@ function App() {
|
|
|
44
44
|
if (cfg.projects_view === 'grid') setView('grid');
|
|
45
45
|
if (cfg.sidebar_collapsed != null) setSidebarCollapsed(!!cfg.sidebar_collapsed);
|
|
46
46
|
if (cfg.sidebar_group) setFilter(cfg.sidebar_group);
|
|
47
|
-
if (
|
|
47
|
+
if (['board', 'creds', 'locks'].includes(cfg.main_view)) setMainView(cfg.main_view);
|
|
48
48
|
} catch { }
|
|
49
49
|
try { const v = await api.get('/api/version'); setVersion(v.c3_version || ''); } catch { }
|
|
50
50
|
}, []);
|
|
@@ -114,6 +114,8 @@ function App() {
|
|
|
114
114
|
<TaskBoard projects={projects} onOpenDrill={openDrill} />
|
|
115
115
|
) : mainView === 'creds' ? (
|
|
116
116
|
<HubCredentials projects={projects} onOpenDrill={openDrill} />
|
|
117
|
+
) : mainView === 'locks' ? (
|
|
118
|
+
<HubLocks projects={projects} onOpenDrill={openDrill} />
|
|
117
119
|
) : (
|
|
118
120
|
<React.Fragment>
|
|
119
121
|
<SummaryBar projects={projects} search={search} setSearch={setSearch}
|