code-context-control 2.79.0__tar.gz → 2.81.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.79.0/code_context_control.egg-info → code_context_control-2.81.0}/PKG-INFO +2 -2
- {code_context_control-2.79.0 → code_context_control-2.81.0}/README.md +1 -1
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/c3.py +12 -4
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/commands/parser.py +15 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_server.py +11 -4
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/hub_ci.js +23 -7
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/mcp_server.py +10 -4
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/ci.py +42 -5
- {code_context_control-2.79.0 → code_context_control-2.81.0/code_context_control.egg-info}/PKG-INFO +2 -2
- {code_context_control-2.79.0 → code_context_control-2.81.0}/code_context_control.egg-info/SOURCES.txt +4 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/pyproject.toml +1 -1
- code_context_control-2.81.0/services/ci_act.py +274 -0
- code_context_control-2.81.0/services/ci_expr.py +435 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/ci_runner.py +295 -19
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/ci_workflow.py +129 -33
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/claude_md.py +1 -1
- code_context_control-2.81.0/tests/test_ci_act.py +418 -0
- code_context_control-2.81.0/tests/test_ci_expr.py +340 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_ci_runner.py +10 -3
- {code_context_control-2.79.0 → code_context_control-2.81.0}/LICENSE +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/__init__.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/_hook_utils.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/commands/__init__.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/commands/common.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/docs.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/edits.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/access.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/bitbucket.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/credentials.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/getting-started.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/index.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/masking.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/oracle.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/shared.css +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/tools.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/workflow.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_access_guard.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_artifact.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_auto_snapshot.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_c3_signal.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_c3read.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_dispatch.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_edit_ledger.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_edit_unlock.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_filter.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_ghost_files.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_pretool_enforce.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_prompt_recall.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_read.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_session_stats.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_terse_advisor.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/app.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/add_project.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/config_editor.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/drill_health.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/drill_panel.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/drill_subprojects.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/drill_tasks.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/drill_views.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/global_search.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/hub_credentials.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/hub_enforcement.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/hub_locks.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/mcp_manager.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/modals.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/project_card.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/project_tree.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/session_drawer.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/settings_modal.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/sidebar.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/summary_bar.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/task_board.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/toasts.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/topbar.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/state.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/mcp_proxy.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/progress.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/server.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/__init__.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/_grants.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/_helpers.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/agent.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/artifacts.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/bitbucket.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/compress.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/credentials.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/delegate.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/edit.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/edit_verify.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/edits.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/federate.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/filter.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/impact.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/jira.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/locks.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/memory.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/override.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/project.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/read.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/search.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/session.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/shell.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/status.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/tasks.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/validate.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/api.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/app.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/access.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/bitbucket.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/chat.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/credentials.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/dashboard.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/edits.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/enforcement.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/instructions.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/jira.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/memory.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/sessions.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/settings.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/sidebar.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/tasks.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/icons.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/pm_shared.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/shared.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/theme.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui_legacy.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui_nano.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/code_context_control.egg-info/dependency_links.txt +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/code_context_control.egg-info/entry_points.txt +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/code_context_control.egg-info/requires.txt +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/code_context_control.egg-info/top_level.txt +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/core/__init__.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/core/config.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/core/ide.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/core/mcp_toml.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/core/web_security.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/__init__.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/config.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/mcp_oracle.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/oracle_server.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/oracle_ui.html +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/__init__.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/activity_reporter.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/api_auth.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/c3_bridge.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/chat_engine.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/chat_poll.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/chat_store.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/cross_memory.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/discovery_audit.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/federated_graph.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/health_checker.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/insight_engine.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/local_session.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/memory_reader.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/memory_writer.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/mobile_api.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/project_scanner.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/review_agent.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/tool_executor.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/tool_registry.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/activity.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/agents.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/app.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/busy.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/chat/conversations.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/chat/input.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/chat/markdown.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/chat/send.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/chat/stream_renderer.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/chat/toolbar.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/core.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/crossgraph.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/header.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/insights.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/projects.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/qrcode.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/settings.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/suggestions.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/theme_tabs.js +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/__init__.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/access_guard.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/access_telemetry.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/activity_log.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/agent_base.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/agent_locks.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/agents.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/artifact_defs.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/artifact_store.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/auto_memory.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/bench/__init__.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/bench/external/__init__.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/bench/external/aider_polyglot.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/bench/external/swe_bench.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/benchmark_dashboard.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/bitbucket_client.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/bitbucket_credentials.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/ci_failures.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/circuit_breaker.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/compressor.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/context_snapshot.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/conversation_store.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/credential_store.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/doc_index.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/e2e_benchmark.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/e2e_evaluator.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/e2e_tasks.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/edit_ledger.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/embedding_index.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/enforcement_policy.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/error_reporting.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/file_memory.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/git_context.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/hub_service.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/indexer.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/jira_client.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/jira_cloud.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/jira_credentials.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/jira_data_center.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/jira_links.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/mask_activation.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/mask_mirror.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/mask_presets.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory_consolidator.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory_distiller.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory_graph.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory_grounder.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory_queue.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory_scorer.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/metrics.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/notifications.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/ollama_bridge.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/ollama_client.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/ollama_credentials.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/output_filter.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/override_grants.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/override_policy.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/override_requests.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/override_wake.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/parser.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/project_manager.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/project_runtime.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/protocol.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/proxy_state.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/repo_map.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/retention.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/retrieval_broker.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/router.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/runtime.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/scanner.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/session_benchmark.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/session_manager.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/session_preloader.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/subprojects.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/task_store.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/telemetry.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/text_index.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/time_tracker.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/tool_classifier.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/transcript_index.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/validation_cache.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/vector_store.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/watcher.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/services/win_subprocess.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/setup.cfg +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_access_builtin_optout.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_access_guard.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_access_guard_meta.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_access_guard_shell_project.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_access_guard_surfaces.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_access_guard_wiring.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_activity_reporter.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_agent_locks.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_aider_polyglot.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_antigravity_transition.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_artifact_store.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_artifact_tool.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_bitbucket_cli_smoke.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_bitbucket_client.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_bitbucket_config_fallback.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_bitbucket_credentials.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_bitbucket_tool.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_c3_shell.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_chat_poll.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_ci_workflow.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_circuit_breaker.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_claude_md_merge.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_cli_smoke.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_compressor_large_file.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_credential_store.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_credentials_routes.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_credentials_tool.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_creds_cli_smoke.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_delegate_cascade.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_delegate_version_probe.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_e2e_benchmark.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_edit_ledger_deadline.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_edit_ledger_hook.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_edit_locking.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_edit_normalization.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_edit_verify.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_embedding_index_deadlock.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_enforcement_flip.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_enforcement_policy.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_enforcement_routes.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_federated_graph.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_filter_backoff.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_ghost_files.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_ghost_generation.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_git_branch_awareness.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_access_guard.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_codex_compat.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_dispatch.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_pretool_enforce.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_prompt_recall.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_smoke.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_state.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_utf8_stdio.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_credentials_route.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_credentials_routes_write.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_enforcement_routes.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_inspect_api.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_locks_routes.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_server_smoke.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_subproject_links.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_ide_workflow_adaptation.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_init_scan.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_install_mcp_entrypoint.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_cli_smoke.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_client.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_config_fallback.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_credentials.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_links.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_routes.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_tool.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_lazy_store_init.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mask_guard.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mask_routes.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mask_surfaces.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mcp_host_guard.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mcp_memory_timeout.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mcp_server_smoke.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mcp_toml.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_memory_distiller.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_memory_graph_api.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_memory_queue.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_memory_system.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mobile_api.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mobile_extras.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mobile_override_routes.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mobile_security_api.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_no_stale_model_pins.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_notification_dedup.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_notification_discipline.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_ollama_credentials.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_api_auth.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_apikey_api.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_c3_bridge.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_chat_engine.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_chat_store.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_discovery_api.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_discovery_audit.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_local_auth.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_ollama_bridge.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_scanner_cache.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_security_fixes.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_session_bootstrap.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_subproject_awareness.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_ui_bundle.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_output_filter.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_override_grants.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_override_grants_mcp.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_override_requests.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_override_wake.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_permissions.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_pm_api.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_project_manager.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_project_manager_merge.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_project_tool.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_read_coercion.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_read_edit_parity.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_related_facts.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_repo_map.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_response_boilerplate.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_retention.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_review_digest.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_service_durability.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_session_benchmark.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_session_budget.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_shell_creds.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_shell_robustness.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_subproject_exclusion.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_subproject_federation.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_subprojects.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_swe_bench.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_task_store.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_task_tool.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_time_tracker.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_token_telemetry.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_tool_registry.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_upgrade_and_version.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_validate.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_vault_write_guard.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_version_sync.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_web_security.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_windows_reliability.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/__init__.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/backend.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/main.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/__init__.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/benchmark_view.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/claudemd_view.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/compress_view.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/index_view.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/init_view.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/mcp_view.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/optimize_view.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/pipe_view.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/projects_view.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/search_view.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/session_view.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/stats.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/ui_view.py +0 -0
- {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/theme.tcss +0 -0
{code_context_control-2.79.0/code_context_control.egg-info → code_context_control-2.81.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-context-control
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.81.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
|
|
@@ -202,7 +202,7 @@ C3 exposes **21 tools** as a native MCP server. Your IDE calls them directly:
|
|
|
202
202
|
| `c3_session` | Snapshot, restore, log decisions, compact history |
|
|
203
203
|
| `c3_impact` | Blast-radius analysis before editing shared symbols |
|
|
204
204
|
| `c3_locks` | Agent leases — who is working on which file, so two agents don't collide (v2.65.0) |
|
|
205
|
-
| `c3_ci` | Run this repo's real `.github/workflows` locally
|
|
205
|
+
| `c3_ci` | Run this repo's real `.github/workflows` locally — in containers via `act` — with structured failures, before pushing (v2.79.0) |
|
|
206
206
|
| `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini |
|
|
207
207
|
| `c3_agent` | Workflows: `review_changes`, `investigate`, `preflight`, `prepare_context`, `validate_compress` |
|
|
208
208
|
| `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter, `verify` (did a failed `c3_edit` still land?) |
|
|
@@ -139,7 +139,7 @@ C3 exposes **21 tools** as a native MCP server. Your IDE calls them directly:
|
|
|
139
139
|
| `c3_session` | Snapshot, restore, log decisions, compact history |
|
|
140
140
|
| `c3_impact` | Blast-radius analysis before editing shared symbols |
|
|
141
141
|
| `c3_locks` | Agent leases — who is working on which file, so two agents don't collide (v2.65.0) |
|
|
142
|
-
| `c3_ci` | Run this repo's real `.github/workflows` locally
|
|
142
|
+
| `c3_ci` | Run this repo's real `.github/workflows` locally — in containers via `act` — with structured failures, before pushing (v2.79.0) |
|
|
143
143
|
| `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini |
|
|
144
144
|
| `c3_agent` | Workflows: `review_changes`, `investigate`, `preflight`, `prepare_context`, `validate_compress` |
|
|
145
145
|
| `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter, `verify` (did a failed `c3_edit` still land?) |
|
|
@@ -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.81.0"
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def _compress_file_cli(compressor, path, mode="smart", **kw):
|
|
@@ -6065,7 +6065,7 @@ def _ci_main(args) -> int:
|
|
|
6065
6065
|
from services import ci_runner as cr
|
|
6066
6066
|
from services.ci_workflow import inspect_project
|
|
6067
6067
|
if sub == "inspect":
|
|
6068
|
-
payload = inspect_project(project_path)
|
|
6068
|
+
payload = inspect_project(project_path, event=getattr(args, "event", "") or "")
|
|
6069
6069
|
elif sub in ("run", "rerun"):
|
|
6070
6070
|
only = None
|
|
6071
6071
|
if sub == "rerun":
|
|
@@ -6078,7 +6078,11 @@ def _ci_main(args) -> int:
|
|
|
6078
6078
|
project_path, selector=getattr(args, "job", ""),
|
|
6079
6079
|
allow_foreign=bool(getattr(args, "allow_foreign", False)),
|
|
6080
6080
|
only=only, timeout=getattr(args, "timeout", 0) or cr.DEFAULT_STEP_TIMEOUT,
|
|
6081
|
-
workflow=getattr(args, "workflow", "")
|
|
6081
|
+
workflow=getattr(args, "workflow", ""),
|
|
6082
|
+
event=getattr(args, "event", "") or "",
|
|
6083
|
+
engine=getattr(args, "engine", "auto") or "auto",
|
|
6084
|
+
allow_side_effects=bool(getattr(args, "allow_side_effects", False)),
|
|
6085
|
+
network=getattr(args, "network", "") or "").to_dict()
|
|
6082
6086
|
elif sub == "runs":
|
|
6083
6087
|
payload = {"runs": cr.list_runs(project_path)}
|
|
6084
6088
|
else:
|
|
@@ -6096,7 +6100,11 @@ def _ci_main(args) -> int:
|
|
|
6096
6100
|
sub, getattr(args, "job", "") or "", getattr(args, "run_id", "") or "",
|
|
6097
6101
|
bool(getattr(args, "allow_foreign", False)),
|
|
6098
6102
|
getattr(args, "workflow", "") or "", getattr(args, "tail", 200) or 200,
|
|
6099
|
-
getattr(args, "timeout", 0) or 0, svc, finalize
|
|
6103
|
+
getattr(args, "timeout", 0) or 0, svc, finalize,
|
|
6104
|
+
getattr(args, "event", "") or "",
|
|
6105
|
+
getattr(args, "engine", "auto") or "auto",
|
|
6106
|
+
bool(getattr(args, "allow_side_effects", False)),
|
|
6107
|
+
getattr(args, "network", "") or "")
|
|
6100
6108
|
print(out)
|
|
6101
6109
|
# Exit code is the finding: a non-full verdict must not read as success to
|
|
6102
6110
|
# a shell script or a pre-push hook.
|
|
@@ -483,6 +483,9 @@ def build_parser(version: str, parse_cli_ide_arg):
|
|
|
483
483
|
sub.add_argument("--path", dest="project_path", default=".",
|
|
484
484
|
help="Project directory (default: current)")
|
|
485
485
|
sub.add_argument("--json", action="store_true", help="Machine-readable output")
|
|
486
|
+
sub.add_argument("--event", default="",
|
|
487
|
+
help="GitHub event to simulate (push, pull_request) — "
|
|
488
|
+
"only needed when an `if:` reads github.event_name")
|
|
486
489
|
return sub
|
|
487
490
|
|
|
488
491
|
_ci_common(ci_subs.add_parser(
|
|
@@ -497,6 +500,16 @@ def build_parser(version: str, parse_cli_ide_arg):
|
|
|
497
500
|
"can never yield FULL_CI_PASS)")
|
|
498
501
|
ci_run.add_argument("--timeout", type=int, default=0,
|
|
499
502
|
help="Per-step timeout in seconds (default 900)")
|
|
503
|
+
ci_run.add_argument("--engine", choices=["auto", "native", "act"],
|
|
504
|
+
default="auto",
|
|
505
|
+
help="auto (default) uses act for Linux jobs when act + "
|
|
506
|
+
"Docker are present, native otherwise")
|
|
507
|
+
ci_run.add_argument("--allow-side-effects", action="store_true",
|
|
508
|
+
help="Let the act engine run jobs that look like they "
|
|
509
|
+
"publish or deploy. Refused by default.")
|
|
510
|
+
ci_run.add_argument("--network", default="",
|
|
511
|
+
help="Container network for the act engine (e.g. `none` "
|
|
512
|
+
"to cut egress). Default: act's own.")
|
|
500
513
|
|
|
501
514
|
ci_rerun = _ci_common(ci_subs.add_parser(
|
|
502
515
|
"rerun", help="Re-run only the jobs that failed in the last run"))
|
|
@@ -517,6 +530,8 @@ def build_parser(version: str, parse_cli_ide_arg):
|
|
|
517
530
|
ci_logs.add_argument("--tail", type=int, default=200)
|
|
518
531
|
|
|
519
532
|
_ci_common(ci_subs.add_parser("runs", help="Recent local CI runs"))
|
|
533
|
+
_ci_common(ci_subs.add_parser(
|
|
534
|
+
"doctor", help="Which execution engines are available on this machine"))
|
|
520
535
|
|
|
521
536
|
# ── Override Requests (v2.69.0, docs/override-requests.md) ──────────
|
|
522
537
|
# Human-only approval surface. A grant is single-use, session-bound,
|
|
@@ -1293,11 +1293,12 @@ _ci_runs: dict = {} # project path -> in-flight state
|
|
|
1293
1293
|
_ci_lock = threading.Lock()
|
|
1294
1294
|
|
|
1295
1295
|
|
|
1296
|
-
def _ci_worker(project: str, job: str, allow_foreign: bool, workflow: str
|
|
1296
|
+
def _ci_worker(project: str, job: str, allow_foreign: bool, workflow: str,
|
|
1297
|
+
engine: str = "auto") -> None:
|
|
1297
1298
|
from services import ci_runner as cr
|
|
1298
1299
|
try:
|
|
1299
1300
|
result = cr.run_ci(project, selector=job, allow_foreign=allow_foreign,
|
|
1300
|
-
workflow=workflow)
|
|
1301
|
+
workflow=workflow, engine=engine)
|
|
1301
1302
|
payload = {"running": False, "done": True, "error": None,
|
|
1302
1303
|
"run_id": result.run_id, "verdict": result.verdict,
|
|
1303
1304
|
"note": result.note}
|
|
@@ -1320,7 +1321,12 @@ def api_ci_inspect():
|
|
|
1320
1321
|
except ValueError as e:
|
|
1321
1322
|
return jsonify({"error": str(e)}), 404
|
|
1322
1323
|
try:
|
|
1323
|
-
|
|
1324
|
+
from services import ci_act
|
|
1325
|
+
data = inspect_project(resolved)
|
|
1326
|
+
# The UI has to be able to say WHY a job cannot run here, and
|
|
1327
|
+
# "install act" is a different answer from "macOS is impossible".
|
|
1328
|
+
data["engines"] = ci_act.availability()
|
|
1329
|
+
return jsonify(data)
|
|
1324
1330
|
except Exception as e:
|
|
1325
1331
|
return jsonify({"error": f"{type(e).__name__}: {e}"}), 500
|
|
1326
1332
|
|
|
@@ -1391,7 +1397,8 @@ def api_ci_run_start():
|
|
|
1391
1397
|
threading.Thread(
|
|
1392
1398
|
target=_ci_worker,
|
|
1393
1399
|
args=(resolved, str(data.get("job") or ""),
|
|
1394
|
-
bool(data.get("allow_foreign")), str(data.get("workflow") or "")
|
|
1400
|
+
bool(data.get("allow_foreign")), str(data.get("workflow") or ""),
|
|
1401
|
+
str(data.get("engine") or "auto")),
|
|
1395
1402
|
daemon=True,
|
|
1396
1403
|
).start()
|
|
1397
1404
|
return jsonify({"started": True})
|
|
@@ -142,6 +142,7 @@ function HubCI({ projects, onOpenDrill }) {
|
|
|
142
142
|
};
|
|
143
143
|
|
|
144
144
|
const jobs = (inspect && inspect.jobs) || [];
|
|
145
|
+
const engines = inspect && inspect.engines;
|
|
145
146
|
const runJobs = ((run && run.jobs) || []).filter(j => j.status !== 'deselected');
|
|
146
147
|
|
|
147
148
|
return (
|
|
@@ -172,8 +173,16 @@ function HubCI({ projects, onOpenDrill }) {
|
|
|
172
173
|
border: `1px solid ${T.border}`, borderRadius: 6, padding: '8px 10px', lineHeight: 1.5,
|
|
173
174
|
}}>
|
|
174
175
|
Runs the repository's real <span className="mono">.github/workflows</span> here —
|
|
175
|
-
C3 does not define a second CI config.
|
|
176
|
-
|
|
176
|
+
C3 does not define a second CI config.{' '}
|
|
177
|
+
{engines && engines.ok ? (
|
|
178
|
+
<span>Linux jobs run in a container via <span className="mono">act</span>{' '}
|
|
179
|
+
({engines.act_version}, docker {engines.docker_version}), real actions included.</span>
|
|
180
|
+
) : (
|
|
181
|
+
<span style={{ color: T.warn }}>
|
|
182
|
+
Container engine unavailable — {(engines && engines.reason) || 'checking…'}
|
|
183
|
+
</span>
|
|
184
|
+
)}{' '}
|
|
185
|
+
macOS jobs can never run locally: there are no macOS containers.
|
|
177
186
|
</div>
|
|
178
187
|
|
|
179
188
|
{err && (
|
|
@@ -236,8 +245,12 @@ function HubCI({ projects, onOpenDrill }) {
|
|
|
236
245
|
<span className="mono" style={{
|
|
237
246
|
color: T.text, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
|
238
247
|
}}>{j.key}</span>
|
|
239
|
-
{j.
|
|
240
|
-
<span className="mono" title={`Ran
|
|
248
|
+
{j.status === 'passed' && j.fidelity === 'container' && (
|
|
249
|
+
<span className="mono" title={`Ran in a container of ${j.runs_on} via act — real actions included.`}
|
|
250
|
+
style={{ fontSize: 10, color: T.accent }}>container</span>
|
|
251
|
+
)}
|
|
252
|
+
{j.status === 'passed' && j.fidelity === 'cross-os' && (
|
|
253
|
+
<span className="mono" title={`Ran on this host, but targets ${j.runs_on}. Indicative, not equivalent.`}
|
|
241
254
|
style={{ fontSize: 10, color: T.blue }}>cross-OS</span>
|
|
242
255
|
)}
|
|
243
256
|
<div style={{ flex: 1 }} />
|
|
@@ -277,9 +290,12 @@ function HubCI({ projects, onOpenDrill }) {
|
|
|
277
290
|
borderTop: `1px solid ${T.border}`, fontSize: 11,
|
|
278
291
|
}}>
|
|
279
292
|
<CiStatusPill
|
|
280
|
-
status={!j.supported
|
|
281
|
-
|
|
282
|
-
|
|
293
|
+
status={(!j.supported && !j.act_could_run) ? 'unsupported'
|
|
294
|
+
: ((j.foreign_runner && !(j.act_could_run && engines && engines.ok))
|
|
295
|
+
? 'foreign' : 'passed')}
|
|
296
|
+
title={(!j.supported && !j.act_could_run) ? (j.blockers || []).join('; ')
|
|
297
|
+
: (j.act_could_run && !j.supported ? 'the act engine can run this'
|
|
298
|
+
: (j.foreign_runner ? `targets ${j.runs_on}` : 'runnable on this host'))} />
|
|
283
299
|
<span className="mono" style={{ color: T.textMuted }}>{j.key}</span>
|
|
284
300
|
{!!(j.needs || []).length && (
|
|
285
301
|
<span className="mono" style={{ fontSize: 10, color: T.textDim }}>
|
|
@@ -769,10 +769,12 @@ async def c3_locks(action: str = "list", paths: str = "", intent: str = "",
|
|
|
769
769
|
@mcp.tool()
|
|
770
770
|
async def c3_ci(action: str = "inspect", job: str = "", run_id: str = "",
|
|
771
771
|
allow_foreign: bool = False, workflow: str = "",
|
|
772
|
-
tail: int = 200, timeout: int = 0,
|
|
772
|
+
tail: int = 200, timeout: int = 0, event: str = "",
|
|
773
|
+
engine: str = "auto", allow_side_effects: bool = False,
|
|
774
|
+
network: str = "", ctx: Context = None) -> str:
|
|
773
775
|
"""RUN THIS REPO'S REAL CI LOCALLY — before pushing, not after.
|
|
774
776
|
Reads .github/workflows/*.yml as the source of truth (no second CI config).
|
|
775
|
-
actions: inspect
|
|
777
|
+
actions: inspect | run | rerun | status | failures | logs | runs | doctor.
|
|
776
778
|
inspect: workflows, the job DAG, and which jobs are runnable on THIS host.
|
|
777
779
|
run: execute in dependency order; job='lint' or 'test (ubuntu-latest, 3.12)'
|
|
778
780
|
selects one (bare job name = all its matrix cells). A job whose dependency
|
|
@@ -783,7 +785,10 @@ async def c3_ci(action: str = "inspect", job: str = "", run_id: str = "",
|
|
|
783
785
|
that means "safe to push". PARTIAL_PASS means something did not run
|
|
784
786
|
(different OS, unsupported action, or you selected a subset); it is NOT a
|
|
785
787
|
green light. Jobs targeting another OS are refused unless allow_foreign=true,
|
|
786
|
-
which runs them and labels the result cross-OS.
|
|
788
|
+
which runs them and labels the result cross-OS.
|
|
789
|
+
event: declare which GitHub event you are simulating ('push',
|
|
790
|
+
'pull_request'). Needed only when an `if:` reads github.event_name —
|
|
791
|
+
there is no event locally, so C3 refuses to guess one."""
|
|
787
792
|
svc = _svc(ctx)
|
|
788
793
|
|
|
789
794
|
def finalize(name, args, resp, summ, **kw):
|
|
@@ -792,7 +797,8 @@ async def c3_ci(action: str = "inspect", job: str = "", run_id: str = "",
|
|
|
792
797
|
from cli.tools.ci import handle_ci
|
|
793
798
|
return await asyncio.to_thread(handle_ci, action, job, run_id,
|
|
794
799
|
allow_foreign, workflow, tail, timeout,
|
|
795
|
-
svc, finalize
|
|
800
|
+
svc, finalize, event, engine,
|
|
801
|
+
allow_side_effects, network)
|
|
796
802
|
|
|
797
803
|
|
|
798
804
|
@mcp.tool()
|
|
@@ -39,7 +39,12 @@ def _job_rows(jobs: list, include_deselected: bool = False) -> list:
|
|
|
39
39
|
if status == cr.DESELECTED and not include_deselected:
|
|
40
40
|
continue
|
|
41
41
|
mark = _MARK.get(status, status.upper())
|
|
42
|
-
|
|
42
|
+
fid = job.get("fidelity", "")
|
|
43
|
+
cross = ""
|
|
44
|
+
if status == cr.PASSED and fid == cr.FIDELITY_CONTAINER:
|
|
45
|
+
cross = " [container]"
|
|
46
|
+
elif status == cr.PASSED and fid == cr.FIDELITY_CROSS_OS:
|
|
47
|
+
cross = " [cross-OS]"
|
|
43
48
|
ms = job.get("duration_ms") or 0
|
|
44
49
|
timing = f" {ms / 1000:.1f}s" if ms else ""
|
|
45
50
|
line = f" {mark:<9} {job.get('key', '?')}{cross}{timing}"
|
|
@@ -74,7 +79,9 @@ def _failure_rows(jobs: list, limit: int = 25) -> list:
|
|
|
74
79
|
|
|
75
80
|
|
|
76
81
|
def handle_ci(action: str, job: str, run_id: str, allow_foreign: bool,
|
|
77
|
-
workflow: str, tail: int, timeout: int, svc, finalize
|
|
82
|
+
workflow: str, tail: int, timeout: int, svc, finalize,
|
|
83
|
+
event: str = "", engine: str = "auto",
|
|
84
|
+
allow_side_effects: bool = False, network: str = "") -> str:
|
|
78
85
|
"""Route c3_ci actions."""
|
|
79
86
|
project = str(svc.project_path)
|
|
80
87
|
action = (action or "inspect").strip().lower()
|
|
@@ -84,7 +91,7 @@ def handle_ci(action: str, job: str, run_id: str, allow_foreign: bool,
|
|
|
84
91
|
|
|
85
92
|
# ── inspect ──────────────────────────────────────────────────────────
|
|
86
93
|
if action in ("", "inspect", "jobs"):
|
|
87
|
-
data = inspect_project(project)
|
|
94
|
+
data = inspect_project(project, event=event)
|
|
88
95
|
if not data["workflows"]:
|
|
89
96
|
return finalize(
|
|
90
97
|
"c3_ci", args,
|
|
@@ -150,7 +157,9 @@ def handle_ci(action: str, job: str, run_id: str, allow_foreign: bool,
|
|
|
150
157
|
|
|
151
158
|
result = cr.run_ci(project, selector=job, allow_foreign=allow_foreign,
|
|
152
159
|
only=only, timeout=timeout or cr.DEFAULT_STEP_TIMEOUT,
|
|
153
|
-
workflow=workflow
|
|
160
|
+
workflow=workflow, event=event, engine=engine,
|
|
161
|
+
allow_side_effects=allow_side_effects,
|
|
162
|
+
network=network)
|
|
154
163
|
run = result.to_dict()
|
|
155
164
|
lines = [_verdict_line(run), f"run {run['run_id']} host={run['host_os']}"]
|
|
156
165
|
fp = run.get("fingerprint") or {}
|
|
@@ -223,6 +232,34 @@ def handle_ci(action: str, job: str, run_id: str, allow_foreign: bool,
|
|
|
223
232
|
f"{row.get('started_at', '')[:19]} {tally}")
|
|
224
233
|
return finalize("c3_ci", args, "\n".join(lines), f"{len(rows)} runs")
|
|
225
234
|
|
|
235
|
+
# ── doctor ───────────────────────────────────────────────────────────
|
|
236
|
+
if action == "doctor":
|
|
237
|
+
from services import ci_act
|
|
238
|
+
state = ci_act.availability()
|
|
239
|
+
lines = [
|
|
240
|
+
"Local CI execution engines:",
|
|
241
|
+
"",
|
|
242
|
+
f" native available — runs jobs whose runs-on matches "
|
|
243
|
+
f"{cr.host_os()}",
|
|
244
|
+
]
|
|
245
|
+
if state["ok"]:
|
|
246
|
+
lines += [
|
|
247
|
+
f" act available — {state['act_version']}, "
|
|
248
|
+
f"docker {state['docker_version']}",
|
|
249
|
+
f" runs Linux jobs in a container "
|
|
250
|
+
f"({ci_act.DEFAULT_IMAGE}), real `uses:` actions included",
|
|
251
|
+
]
|
|
252
|
+
else:
|
|
253
|
+
lines += [" act UNAVAILABLE", f" {state['reason']}"]
|
|
254
|
+
lines += [
|
|
255
|
+
"",
|
|
256
|
+
"macOS jobs cannot run locally on any engine — there are no macOS "
|
|
257
|
+
"containers, so a matrix containing them can never reach "
|
|
258
|
+
"FULL_CI_PASS here.",
|
|
259
|
+
]
|
|
260
|
+
return finalize("c3_ci", args, "\n".join(lines),
|
|
261
|
+
"act ready" if state["ok"] else "act unavailable")
|
|
262
|
+
|
|
226
263
|
return finalize("c3_ci", args,
|
|
227
264
|
f"Unknown action '{action}'. Use: inspect, run, rerun, "
|
|
228
|
-
"status, failures, logs, runs.", "unknown action")
|
|
265
|
+
"status, failures, logs, runs, doctor.", "unknown action")
|
{code_context_control-2.79.0 → code_context_control-2.81.0/code_context_control.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-context-control
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.81.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
|
|
@@ -202,7 +202,7 @@ C3 exposes **21 tools** as a native MCP server. Your IDE calls them directly:
|
|
|
202
202
|
| `c3_session` | Snapshot, restore, log decisions, compact history |
|
|
203
203
|
| `c3_impact` | Blast-radius analysis before editing shared symbols |
|
|
204
204
|
| `c3_locks` | Agent leases — who is working on which file, so two agents don't collide (v2.65.0) |
|
|
205
|
-
| `c3_ci` | Run this repo's real `.github/workflows` locally
|
|
205
|
+
| `c3_ci` | Run this repo's real `.github/workflows` locally — in containers via `act` — with structured failures, before pushing (v2.79.0) |
|
|
206
206
|
| `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini |
|
|
207
207
|
| `c3_agent` | Workflows: `review_changes`, `investigate`, `preflight`, `prepare_context`, `validate_compress` |
|
|
208
208
|
| `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter, `verify` (did a failed `c3_edit` still land?) |
|
|
@@ -185,6 +185,8 @@ services/auto_memory.py
|
|
|
185
185
|
services/benchmark_dashboard.py
|
|
186
186
|
services/bitbucket_client.py
|
|
187
187
|
services/bitbucket_credentials.py
|
|
188
|
+
services/ci_act.py
|
|
189
|
+
services/ci_expr.py
|
|
188
190
|
services/ci_failures.py
|
|
189
191
|
services/ci_runner.py
|
|
190
192
|
services/ci_workflow.py
|
|
@@ -280,6 +282,8 @@ tests/test_bitbucket_tool.py
|
|
|
280
282
|
tests/test_c3_bridge_project_artifacts.py
|
|
281
283
|
tests/test_c3_shell.py
|
|
282
284
|
tests/test_chat_poll.py
|
|
285
|
+
tests/test_ci_act.py
|
|
286
|
+
tests/test_ci_expr.py
|
|
283
287
|
tests/test_ci_runner.py
|
|
284
288
|
tests/test_ci_workflow.py
|
|
285
289
|
tests/test_circuit_breaker.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code-context-control"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.81.0"
|
|
8
8
|
description = "Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""AgentCI — the `act` execution engine (Linux jobs in real containers).
|
|
2
|
+
|
|
3
|
+
The native engine runs a job's shell steps on this machine. That is faithful
|
|
4
|
+
when `runs-on` matches the host and an approximation otherwise, and it cannot
|
|
5
|
+
run `uses:` actions at all. This engine hands a Linux job to `nektos/act`,
|
|
6
|
+
which runs it in a container using GitHub's own workflow semantics — real
|
|
7
|
+
actions included.
|
|
8
|
+
|
|
9
|
+
What that buys, measured on this repository from Windows: 3 of 15 jobs
|
|
10
|
+
runnable natively, 11 with containers. The remaining four are macOS cells, and
|
|
11
|
+
no amount of Docker fixes those — **there are no macOS containers**, so a full
|
|
12
|
+
local pass on a matrix containing them stays out of reach. Saying so is part of
|
|
13
|
+
the job.
|
|
14
|
+
|
|
15
|
+
Three things learned by probing rather than by reading, all load-bearing:
|
|
16
|
+
|
|
17
|
+
1. **`--bind` is required on Windows.** act's default copies the workspace into
|
|
18
|
+
the container; against a Windows host path that silently produced an empty
|
|
19
|
+
directory and every step failed on missing files. `--bind` mounts the real
|
|
20
|
+
working tree and works even on a mapped drive whose path contains spaces and
|
|
21
|
+
parentheses. The trade-off is that the container writes into the real tree —
|
|
22
|
+
the same thing the native engine already does, so behaviour is consistent.
|
|
23
|
+
|
|
24
|
+
2. **`-P <label>=<image>` must always be passed.** Without it act prompts
|
|
25
|
+
interactively on first use to choose an image size, which would hang any
|
|
26
|
+
automated run.
|
|
27
|
+
|
|
28
|
+
3. **`-W <file>` is mandatory when two workflows share a job name.** act says so
|
|
29
|
+
itself on this repo, where `CI` and `Release` both define `build` — the same
|
|
30
|
+
collision the DAG scopes around.
|
|
31
|
+
|
|
32
|
+
Safety: this engine never passes real secrets or tokens. act reads `.secrets`
|
|
33
|
+
and `.env` from the repository by default, so both are explicitly pointed at
|
|
34
|
+
empty files. A publish step therefore executes and fails at authentication
|
|
35
|
+
instead of publishing, which is the intended outcome for a local run.
|
|
36
|
+
"""
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import os
|
|
40
|
+
import re
|
|
41
|
+
import shutil
|
|
42
|
+
import subprocess
|
|
43
|
+
import tempfile
|
|
44
|
+
import time
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
|
|
47
|
+
# act's community runner images. These mirror a good deal of the GitHub
|
|
48
|
+
# runner's preinstalled toolchain; a bare `ubuntu:24.04` does not, and a job
|
|
49
|
+
# that assumes a preinstalled tool then fails locally while passing in CI. A
|
|
50
|
+
# false red is the safe direction, but it is still noise, so default to the
|
|
51
|
+
# image built for the purpose.
|
|
52
|
+
DEFAULT_IMAGE = "catthehacker/ubuntu:act-latest"
|
|
53
|
+
RUNNER_IMAGES = {
|
|
54
|
+
"ubuntu-latest": DEFAULT_IMAGE,
|
|
55
|
+
"ubuntu-24.04": "catthehacker/ubuntu:act-24.04",
|
|
56
|
+
"ubuntu-22.04": "catthehacker/ubuntu:act-22.04",
|
|
57
|
+
"ubuntu-20.04": "catthehacker/ubuntu:act-20.04",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Pulling a runner image is ~1 GB on first use, so the act engine gets its own,
|
|
61
|
+
# larger default ceiling than a native shell step.
|
|
62
|
+
DEFAULT_ACT_TIMEOUT = 3600
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def image_for(runs_on: str) -> str:
|
|
66
|
+
label = str(runs_on or "").strip().lower()
|
|
67
|
+
return RUNNER_IMAGES.get(label, DEFAULT_IMAGE)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def find_act() -> str:
|
|
71
|
+
"""Path to the act binary. C3_ACT_PATH wins, then PATH."""
|
|
72
|
+
override = (os.environ.get("C3_ACT_PATH") or "").strip()
|
|
73
|
+
if override and Path(override).is_file():
|
|
74
|
+
return override
|
|
75
|
+
return shutil.which("act") or ""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _probe(cmd: list, timeout: int = 30) -> tuple:
|
|
79
|
+
try:
|
|
80
|
+
out = subprocess.run(cmd, capture_output=True, text=True,
|
|
81
|
+
timeout=timeout, stdin=subprocess.DEVNULL,
|
|
82
|
+
**({"creationflags": subprocess.CREATE_NO_WINDOW}
|
|
83
|
+
if os.name == "nt" else {}))
|
|
84
|
+
return out.returncode, (out.stdout or "") + (out.stderr or "")
|
|
85
|
+
except Exception as exc:
|
|
86
|
+
return 127, f"{type(exc).__name__}: {exc}"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def availability() -> dict:
|
|
90
|
+
"""Can this engine run at all? Reports WHY not, never just False.
|
|
91
|
+
|
|
92
|
+
Both halves are checked because they fail independently and a user with
|
|
93
|
+
act installed but Docker stopped deserves to be told which one to fix.
|
|
94
|
+
"""
|
|
95
|
+
act = find_act()
|
|
96
|
+
info: dict = {"ok": False, "act": act, "act_version": "",
|
|
97
|
+
"docker": False, "docker_version": "", "reason": ""}
|
|
98
|
+
if not act:
|
|
99
|
+
info["reason"] = ("`act` is not installed. Install it with "
|
|
100
|
+
"`winget install nektos.act` (Windows), "
|
|
101
|
+
"`brew install act` (macOS), or see "
|
|
102
|
+
"https://nektosact.com — then re-run.")
|
|
103
|
+
return info
|
|
104
|
+
rc, out = _probe([act, "--version"])
|
|
105
|
+
if rc != 0:
|
|
106
|
+
info["reason"] = f"`act --version` failed: {out.strip()[:200]}"
|
|
107
|
+
return info
|
|
108
|
+
info["act_version"] = out.strip().splitlines()[0] if out.strip() else ""
|
|
109
|
+
|
|
110
|
+
rc, out = _probe(["docker", "version", "--format", "{{.Server.Version}}"])
|
|
111
|
+
if rc != 0:
|
|
112
|
+
info["reason"] = ("Docker is not reachable — the act engine needs a "
|
|
113
|
+
"running daemon. Start Docker Desktop (or dockerd) "
|
|
114
|
+
f"and retry. Probe said: {out.strip()[:160]}")
|
|
115
|
+
return info
|
|
116
|
+
info["docker"] = True
|
|
117
|
+
info["docker_version"] = out.strip().splitlines()[-1] if out.strip() else ""
|
|
118
|
+
info["ok"] = True
|
|
119
|
+
return info
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ── Side-effect gate ────────────────────────────────────────────────────────
|
|
123
|
+
# With a real action runner, jobs that were previously unrunnable become
|
|
124
|
+
# runnable — including ones whose entire purpose is to publish. Capability no
|
|
125
|
+
# longer protects anybody, so policy has to.
|
|
126
|
+
PUBLISHING_ACTIONS = (
|
|
127
|
+
"pypa/gh-action-pypi-publish",
|
|
128
|
+
"softprops/action-gh-release",
|
|
129
|
+
"ncipollo/release-action",
|
|
130
|
+
"actions/create-release",
|
|
131
|
+
"docker/build-push-action",
|
|
132
|
+
"peaceiris/actions-gh-pages",
|
|
133
|
+
"actions/deploy-pages",
|
|
134
|
+
"js-devtools/npm-publish",
|
|
135
|
+
"gradle/gradle-publish",
|
|
136
|
+
"cycjimmy/semantic-release-action",
|
|
137
|
+
)
|
|
138
|
+
_PUBLISH_CMD = re.compile(
|
|
139
|
+
r"\b(twine\s+upload|npm\s+publish|cargo\s+publish|gh\s+release\s+create"
|
|
140
|
+
r"|docker\s+push|poetry\s+publish|aws\s+s3\s+(cp|sync)|kubectl\s+apply"
|
|
141
|
+
r"|terraform\s+apply|gcloud\s+.*deploy)\b", re.IGNORECASE)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def side_effects(inst) -> list:
|
|
145
|
+
"""Reasons this job looks like it publishes or deploys something."""
|
|
146
|
+
found: list = []
|
|
147
|
+
for step in getattr(inst, "steps", []):
|
|
148
|
+
base = str(getattr(step, "uses", "") or "").split("@", 1)[0].lower()
|
|
149
|
+
if base in PUBLISHING_ACTIONS:
|
|
150
|
+
found.append(f"step {step.index} uses `{step.uses}`")
|
|
151
|
+
match = _PUBLISH_CMD.search(str(getattr(step, "run", "") or ""))
|
|
152
|
+
if match:
|
|
153
|
+
found.append(f"step {step.index} runs `{match.group(0)}`")
|
|
154
|
+
return found
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ── Command construction ────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
def build_command(inst, project, act_path: str, event: str = "",
|
|
160
|
+
image: str = "", network: str = "",
|
|
161
|
+
secret_file: str = "", env_file: str = "",
|
|
162
|
+
artifact_dir: str = "", pull: bool = True) -> list:
|
|
163
|
+
"""The exact argv handed to act. Pure, so it is unit-testable."""
|
|
164
|
+
cmd: list = [act_path]
|
|
165
|
+
if event:
|
|
166
|
+
cmd.append(event)
|
|
167
|
+
|
|
168
|
+
workflow_path = getattr(inst, "workflow_path", "") or ""
|
|
169
|
+
if workflow_path:
|
|
170
|
+
try:
|
|
171
|
+
rel = Path(workflow_path).resolve().relative_to(Path(project).resolve())
|
|
172
|
+
cmd += ["-W", str(rel).replace("\\", "/")]
|
|
173
|
+
except ValueError:
|
|
174
|
+
cmd += ["-W", str(workflow_path)]
|
|
175
|
+
|
|
176
|
+
cmd += ["-j", inst.job_id]
|
|
177
|
+
|
|
178
|
+
for key, value in sorted((inst.matrix or {}).items()):
|
|
179
|
+
cmd += ["--matrix", f"{key}:{value}"]
|
|
180
|
+
|
|
181
|
+
cmd += ["-P", f"{inst.runs_on}={image or image_for(inst.runs_on)}"]
|
|
182
|
+
|
|
183
|
+
# See the module docstring: without --bind the workspace arrives empty on
|
|
184
|
+
# Windows and every step fails on missing files.
|
|
185
|
+
cmd.append("--bind")
|
|
186
|
+
cmd.append(f"--pull={'true' if pull else 'false'}")
|
|
187
|
+
|
|
188
|
+
if network:
|
|
189
|
+
cmd += ["--network", network]
|
|
190
|
+
# Never let act source the repository's real .secrets / .env.
|
|
191
|
+
cmd += ["--secret-file", secret_file or os.devnull]
|
|
192
|
+
cmd += ["--env-file", env_file or os.devnull]
|
|
193
|
+
if artifact_dir:
|
|
194
|
+
cmd += ["--artifact-server-path", artifact_dir]
|
|
195
|
+
return cmd
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# act prefixes every line it emits with `[Workflow/job]`, and marks actual
|
|
199
|
+
# program output with a `|` gutter:
|
|
200
|
+
#
|
|
201
|
+
# [CI/lint] ⭐ Run Main ruff check . <- act's own narration
|
|
202
|
+
# [CI/lint] | app/x.py:3:1: F401 ... <- what the command printed
|
|
203
|
+
#
|
|
204
|
+
# Handing the raw log to the failure parsers captures the prefix as part of the
|
|
205
|
+
# filename — `file=[CI/lint] ⭐ Run Main echo "app/x.py` — which makes the
|
|
206
|
+
# structured failure useless. Parse the gutter lines only.
|
|
207
|
+
_ACT_OUTPUT_LINE = re.compile(r"^\[[^\]]*\]\s{0,3}\|\s?(.*)$")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def program_output(text: str) -> str:
|
|
211
|
+
"""Just what the commands printed, with act's prefixes removed.
|
|
212
|
+
|
|
213
|
+
Falls back to the raw text when nothing matches, so an act failure that
|
|
214
|
+
never reached a command (a bad image, a daemon error) still reaches the
|
|
215
|
+
parsers and the reader rather than becoming an empty log.
|
|
216
|
+
"""
|
|
217
|
+
lines = [m.group(1) for m in
|
|
218
|
+
(_ACT_OUTPUT_LINE.match(ln) for ln in (text or "").splitlines())
|
|
219
|
+
if m]
|
|
220
|
+
return "\n".join(lines) if lines else (text or "")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def run_job(inst, project, timeout: int = DEFAULT_ACT_TIMEOUT,
|
|
224
|
+
event: str = "", network: str = "", artifact_dir: str = "",
|
|
225
|
+
act_path: str = "") -> dict:
|
|
226
|
+
"""Run one job through act. Returns {exit_code, output, timed_out, command}."""
|
|
227
|
+
act_path = act_path or find_act()
|
|
228
|
+
if not act_path:
|
|
229
|
+
return {"exit_code": 127, "output": "act is not installed",
|
|
230
|
+
"timed_out": False, "command": ""}
|
|
231
|
+
|
|
232
|
+
tmpdir = tempfile.mkdtemp(prefix="c3act-")
|
|
233
|
+
empty = Path(tmpdir) / "empty"
|
|
234
|
+
empty.write_text("", encoding="utf-8")
|
|
235
|
+
|
|
236
|
+
cmd = build_command(inst, project, act_path, event=event, network=network,
|
|
237
|
+
secret_file=str(empty), env_file=str(empty),
|
|
238
|
+
artifact_dir=artifact_dir)
|
|
239
|
+
|
|
240
|
+
from cli.tools.shell import _kill_tree, _popen_kwargs
|
|
241
|
+
|
|
242
|
+
start = time.time()
|
|
243
|
+
timed_out = False
|
|
244
|
+
try:
|
|
245
|
+
proc = subprocess.Popen(
|
|
246
|
+
cmd, cwd=str(project), stdout=subprocess.PIPE,
|
|
247
|
+
stderr=subprocess.STDOUT, text=True, encoding="utf-8",
|
|
248
|
+
errors="replace", **_popen_kwargs(),
|
|
249
|
+
)
|
|
250
|
+
except (OSError, ValueError) as exc:
|
|
251
|
+
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
252
|
+
return {"exit_code": 126, "output": f"{type(exc).__name__}: {exc}",
|
|
253
|
+
"timed_out": False, "command": " ".join(cmd)}
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
output, _ = proc.communicate(timeout=timeout)
|
|
257
|
+
except subprocess.TimeoutExpired:
|
|
258
|
+
_kill_tree(proc)
|
|
259
|
+
try:
|
|
260
|
+
output, _ = proc.communicate(timeout=5)
|
|
261
|
+
except Exception:
|
|
262
|
+
output = ""
|
|
263
|
+
timed_out = True
|
|
264
|
+
output = (output or "") + (
|
|
265
|
+
f"\n[c3:ci] act exceeded {timeout}s and its container tree was killed\n")
|
|
266
|
+
|
|
267
|
+
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
268
|
+
return {
|
|
269
|
+
"exit_code": -1 if timed_out else (proc.returncode or 0),
|
|
270
|
+
"output": output or "",
|
|
271
|
+
"timed_out": timed_out,
|
|
272
|
+
"duration_ms": round((time.time() - start) * 1000),
|
|
273
|
+
"command": " ".join(cmd),
|
|
274
|
+
}
|