alysis-code 0.13.0__py3-none-any.whl
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.
- alysis_code/__init__.py +3 -0
- alysis_code/__main__.py +4 -0
- alysis_code/_build_info.py +14 -0
- alysis_code/account_login.py +468 -0
- alysis_code/agent/README.md +35 -0
- alysis_code/agent/__init__.py +11 -0
- alysis_code/agent/acceptance_contract.py +2217 -0
- alysis_code/agent/blast_radius.py +1403 -0
- alysis_code/agent/cache_keepalive.py +227 -0
- alysis_code/agent/completion_certificate.py +366 -0
- alysis_code/agent/completion_gate.py +306 -0
- alysis_code/agent/empty_response_stall.py +403 -0
- alysis_code/agent/errors.py +28 -0
- alysis_code/agent/llm_calls.py +475 -0
- alysis_code/agent/mutation_classification.py +227 -0
- alysis_code/agent/prompt_context.py +2508 -0
- alysis_code/agent/read_ledger.py +253 -0
- alysis_code/agent/regression_baseline.py +642 -0
- alysis_code/agent/reproduction_first.py +610 -0
- alysis_code/agent/sensitive_output.py +629 -0
- alysis_code/agent/session.py +3218 -0
- alysis_code/agent/steering.py +191 -0
- alysis_code/agent/subagent_execution.py +5177 -0
- alysis_code/agent/subagent_workspace.py +666 -0
- alysis_code/agent/tools_assembly.py +4728 -0
- alysis_code/agent/turn/__init__.py +101 -0
- alysis_code/agent/turn/core.py +8483 -0
- alysis_code/agent/turn/events.py +113 -0
- alysis_code/agent/turn/exploration.py +590 -0
- alysis_code/agent/turn/interventions.py +65 -0
- alysis_code/agent/turn/read_cache.py +420 -0
- alysis_code/agent/turn/snapshot.py +179 -0
- alysis_code/agent/turn_contract.py +661 -0
- alysis_code/agent/turn_path.py +129 -0
- alysis_code/agent/verification.py +2885 -0
- alysis_code/agent/verification_commands.py +512 -0
- alysis_code/agent/verification_evidence.py +738 -0
- alysis_code/agent_loop.py +693 -0
- alysis_code/agent_runtimes/__init__.py +51 -0
- alysis_code/agent_runtimes/base.py +114 -0
- alysis_code/agent_runtimes/builtins.py +129 -0
- alysis_code/agent_runtimes/codex_cli.py +664 -0
- alysis_code/agent_runtimes/host.py +263 -0
- alysis_code/agent_runtimes/registry.py +64 -0
- alysis_code/agent_runtimes/service.py +150 -0
- alysis_code/agentbox_client.py +416 -0
- alysis_code/agentbox_integration.py +310 -0
- alysis_code/alysis_cloud.py +152 -0
- alysis_code/approval_scope.py +276 -0
- alysis_code/assets/README.md +33 -0
- alysis_code/assets/__init__.py +126 -0
- alysis_code/assets/asset_read_core.py +281 -0
- alysis_code/assets/budget_allocator.py +456 -0
- alysis_code/assets/comprehender.py +759 -0
- alysis_code/assets/index.py +654 -0
- alysis_code/assets/ingestion.py +275 -0
- alysis_code/assets/legacy_migration.py +413 -0
- alysis_code/assets/models.py +263 -0
- alysis_code/assets/ocr.py +239 -0
- alysis_code/assets/owl/ascii/f-000.txt +13 -0
- alysis_code/assets/owl/ascii/f-001.txt +13 -0
- alysis_code/assets/owl/ascii/f-002.txt +13 -0
- alysis_code/assets/owl/ascii/f-003.txt +13 -0
- alysis_code/assets/owl/ascii/f-004.txt +13 -0
- alysis_code/assets/owl/ascii/f-005.txt +13 -0
- alysis_code/assets/owl/ascii/f-006.txt +13 -0
- alysis_code/assets/owl/ascii/f-007.txt +13 -0
- alysis_code/assets/owl/ascii/f-008.txt +13 -0
- alysis_code/assets/owl/ascii/f-009.txt +13 -0
- alysis_code/assets/owl/ascii/f-010.txt +13 -0
- alysis_code/assets/owl/ascii/f-011.txt +13 -0
- alysis_code/assets/owl/ascii/f-012.txt +13 -0
- alysis_code/assets/owl/ascii/f-013.txt +13 -0
- alysis_code/assets/owl/ascii/f-014.txt +13 -0
- alysis_code/assets/owl/ascii/f-015.txt +13 -0
- alysis_code/assets/owl/ascii/f-016.txt +13 -0
- alysis_code/assets/owl/ascii/f-017.txt +13 -0
- alysis_code/assets/owl/ascii/f-018.txt +13 -0
- alysis_code/assets/owl/ascii/f-019.txt +13 -0
- alysis_code/assets/owl/ascii/f-020.txt +13 -0
- alysis_code/assets/owl/index.html +98 -0
- alysis_code/assets/owl/show-owl.sh +761 -0
- alysis_code/assets/paths.py +49 -0
- alysis_code/assets/plan_binding.py +326 -0
- alysis_code/assets/planner_context.py +466 -0
- alysis_code/assets/planner_tools.py +184 -0
- alysis_code/assets/prompts.py +101 -0
- alysis_code/assets/replanner_context.py +239 -0
- alysis_code/assets/surface.py +521 -0
- alysis_code/assets/untrusted_content.py +48 -0
- alysis_code/assets/usage_logger.py +94 -0
- alysis_code/assets/worker_mirror.py +428 -0
- alysis_code/assets/worker_section.py +303 -0
- alysis_code/assets/worker_tools.py +468 -0
- alysis_code/atomic_io.py +83 -0
- alysis_code/auth_diagnostics.py +272 -0
- alysis_code/background_runner.py +366 -0
- alysis_code/branding.py +270 -0
- alysis_code/budget_policy.py +390 -0
- alysis_code/build_identity.py +465 -0
- alysis_code/builtin_hooks/__init__.py +7 -0
- alysis_code/builtin_hooks/notify_done_windows.py +65 -0
- alysis_code/bwrap_etc.py +76 -0
- alysis_code/cancellation.py +41 -0
- alysis_code/capabilities.py +137 -0
- alysis_code/chatgpt_codex_static_provider.py +133 -0
- alysis_code/cli.py +51 -0
- alysis_code/cli_impl/__init__.py +1 -0
- alysis_code/cli_impl/assets_cli.py +537 -0
- alysis_code/cli_impl/assets_modal.py +412 -0
- alysis_code/cli_impl/chat/__init__.py +156 -0
- alysis_code/cli_impl/chat/commands.py +2616 -0
- alysis_code/cli_impl/chat/loop.py +4508 -0
- alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
- alysis_code/cli_impl/chat/rendering.py +444 -0
- alysis_code/cli_impl/chat/state.py +124 -0
- alysis_code/cli_impl/chat_resume.py +830 -0
- alysis_code/cli_impl/chat_slash_completer.py +258 -0
- alysis_code/cli_impl/commands/__init__.py +11 -0
- alysis_code/cli_impl/commands/_shared.py +89 -0
- alysis_code/cli_impl/commands/auth.py +623 -0
- alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
- alysis_code/cli_impl/commands/chat_state.py +158 -0
- alysis_code/cli_impl/commands/chat_status.py +1248 -0
- alysis_code/cli_impl/commands/chat_terminal.py +942 -0
- alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
- alysis_code/cli_impl/commands/cli_common.py +1223 -0
- alysis_code/cli_impl/commands/cli_surface.py +77 -0
- alysis_code/cli_impl/commands/config.py +131 -0
- alysis_code/cli_impl/commands/conventions.py +85 -0
- alysis_code/cli_impl/commands/execution_helpers.py +350 -0
- alysis_code/cli_impl/commands/extensions.py +401 -0
- alysis_code/cli_impl/commands/forge.py +1282 -0
- alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
- alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
- alysis_code/cli_impl/commands/hooks.py +737 -0
- alysis_code/cli_impl/commands/ide_bridge.py +31 -0
- alysis_code/cli_impl/commands/mcp.py +700 -0
- alysis_code/cli_impl/commands/profile.py +453 -0
- alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
- alysis_code/cli_impl/commands/report.py +88 -0
- alysis_code/cli_impl/commands/root.py +1118 -0
- alysis_code/cli_impl/commands/sandbox.py +184 -0
- alysis_code/cli_impl/commands/server.py +54 -0
- alysis_code/cli_impl/commands/sessions.py +252 -0
- alysis_code/cli_impl/commands/skills.py +404 -0
- alysis_code/cli_impl/commands/startup.py +946 -0
- alysis_code/cli_impl/commands/tools.py +335 -0
- alysis_code/cli_impl/commands/update.py +364 -0
- alysis_code/cli_impl/commands/welcome.py +972 -0
- alysis_code/cli_impl/config_menu.py +3882 -0
- alysis_code/cli_impl/forge.py +4509 -0
- alysis_code/cli_impl/forge_recovery.py +485 -0
- alysis_code/cli_impl/setup_wizard.py +2409 -0
- alysis_code/cli_impl/tui/__init__.py +58 -0
- alysis_code/cli_impl/tui/app.py +4551 -0
- alysis_code/cli_impl/tui/config.py +32 -0
- alysis_code/cli_impl/tui/config_flow.py +2754 -0
- alysis_code/cli_impl/tui/config_overlay.py +566 -0
- alysis_code/cli_impl/tui/content.py +78 -0
- alysis_code/cli_impl/tui/footer.py +218 -0
- alysis_code/cli_impl/tui/forge_status.py +136 -0
- alysis_code/cli_impl/tui/markdown.py +244 -0
- alysis_code/cli_impl/tui/owl.py +109 -0
- alysis_code/cli_impl/tui/plan_meta.py +477 -0
- alysis_code/cli_impl/tui/setup_app.py +519 -0
- alysis_code/cli_impl/tui/setup_flow.py +1622 -0
- alysis_code/cli_impl/tui/state.py +101 -0
- alysis_code/cli_impl/tui/subagent_identity.py +66 -0
- alysis_code/cli_impl/tui/subagent_panel.py +186 -0
- alysis_code/cli_impl/tui/surface.py +796 -0
- alysis_code/cli_impl/tui/transcript.py +514 -0
- alysis_code/cli_impl/tui/update_prompt.py +79 -0
- alysis_code/cli_impl/tui/workspace_guard.py +384 -0
- alysis_code/clipboard.py +172 -0
- alysis_code/code_review.py +1211 -0
- alysis_code/compaction/__init__.py +28 -0
- alysis_code/compaction/conversation_compactor.py +2932 -0
- alysis_code/compaction/importance.py +177 -0
- alysis_code/compaction/settings.py +297 -0
- alysis_code/compaction/tool_output_offload.py +447 -0
- alysis_code/config.py +3509 -0
- alysis_code/conflict_auto_resolver.py +895 -0
- alysis_code/context/__init__.py +1 -0
- alysis_code/context/tool_schema_budgeter.py +220 -0
- alysis_code/crash_diagnostics.py +282 -0
- alysis_code/custom_tools/README.md +34 -0
- alysis_code/custom_tools/__init__.py +43 -0
- alysis_code/custom_tools/discovery.py +903 -0
- alysis_code/custom_tools/runtime.py +1516 -0
- alysis_code/custom_tools/session.py +227 -0
- alysis_code/custom_tools/trust.py +232 -0
- alysis_code/diff_paths.py +113 -0
- alysis_code/direction_change.py +293 -0
- alysis_code/dispatch_timing.py +306 -0
- alysis_code/durable_service_manager.py +1236 -0
- alysis_code/edit_discipline.py +659 -0
- alysis_code/error_text.py +73 -0
- alysis_code/execution_budget.py +411 -0
- alysis_code/execution_context.py +915 -0
- alysis_code/execution_deadline.py +1065 -0
- alysis_code/execution_shared.py +1904 -0
- alysis_code/extensions/README.md +30 -0
- alysis_code/extensions/__init__.py +93 -0
- alysis_code/extensions/activation.py +138 -0
- alysis_code/extensions/install.py +1436 -0
- alysis_code/extensions/manifest.py +487 -0
- alysis_code/extensions/models.py +74 -0
- alysis_code/extensions/paths.py +56 -0
- alysis_code/extensions/registry.json +4 -0
- alysis_code/extensions/registry.py +52 -0
- alysis_code/extensions/state.py +83 -0
- alysis_code/extensions/workspace_trust.py +101 -0
- alysis_code/failed_task_evidence.py +369 -0
- alysis_code/failure_category.py +315 -0
- alysis_code/feedback_report.py +1647 -0
- alysis_code/file_classification.py +485 -0
- alysis_code/forge.py +2064 -0
- alysis_code/forge_completion.py +362 -0
- alysis_code/forge_events.py +475 -0
- alysis_code/frontmatter_utils.py +95 -0
- alysis_code/git_evidence.py +1181 -0
- alysis_code/git_ops.py +560 -0
- alysis_code/git_safe.py +62 -0
- alysis_code/git_worktrees.py +190 -0
- alysis_code/hooks/README.md +33 -0
- alysis_code/hooks/__init__.py +67 -0
- alysis_code/hooks/audit.py +171 -0
- alysis_code/hooks/config.py +225 -0
- alysis_code/hooks/dispatcher.py +1110 -0
- alysis_code/hooks/models.py +447 -0
- alysis_code/hooks/trust.py +202 -0
- alysis_code/host_actions.py +543 -0
- alysis_code/host_browser.py +103 -0
- alysis_code/ide/__init__.py +5 -0
- alysis_code/ide/activity_events.py +399 -0
- alysis_code/ide/approvals.py +337 -0
- alysis_code/ide/artifacts.py +153 -0
- alysis_code/ide/browser_egress_proxy.py +1076 -0
- alysis_code/ide/cdp_websocket_transport.py +1192 -0
- alysis_code/ide/change_ledger.py +1721 -0
- alysis_code/ide/context_blocks.py +979 -0
- alysis_code/ide/event_stream.py +531 -0
- alysis_code/ide/forge_protocol.py +3112 -0
- alysis_code/ide/forge_request_ledger.py +737 -0
- alysis_code/ide/health.py +965 -0
- alysis_code/ide/managed_browser.py +2251 -0
- alysis_code/ide/management_protocol.py +3414 -0
- alysis_code/ide/mcp_oauth_coordinator.py +744 -0
- alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
- alysis_code/ide/prompt_queue.py +1070 -0
- alysis_code/ide/protocol.py +191 -0
- alysis_code/ide/resumable_swarm.py +1543 -0
- alysis_code/ide/session_search.py +295 -0
- alysis_code/ide/stdio_bridge.py +9935 -0
- alysis_code/ide/structured_state.py +1579 -0
- alysis_code/ide/swarm_protocol.py +816 -0
- alysis_code/integration_gate.py +506 -0
- alysis_code/interactive_input_guard.py +39 -0
- alysis_code/interactive_plan_mode.py +26 -0
- alysis_code/internal_artifacts.py +179 -0
- alysis_code/knowledge_base.py +1409 -0
- alysis_code/knowledge_capture.py +1190 -0
- alysis_code/knowledge_librarian.py +605 -0
- alysis_code/language_policy.py +34 -0
- alysis_code/litellm_static_provider.py +535 -0
- alysis_code/llm/__init__.py +1 -0
- alysis_code/llm/anthropic_messages.py +2288 -0
- alysis_code/llm/base.py +71 -0
- alysis_code/llm/cache_capabilities.py +985 -0
- alysis_code/llm/cache_control_blocks.py +244 -0
- alysis_code/llm/cache_policy.py +388 -0
- alysis_code/llm/factory.py +373 -0
- alysis_code/llm/gemini_generate_content.py +2652 -0
- alysis_code/llm/gemini_interactions.py +739 -0
- alysis_code/llm/metadata.py +450 -0
- alysis_code/llm/openai_compat.py +2947 -0
- alysis_code/llm/openai_responses.py +2604 -0
- alysis_code/llm/protocols.py +609 -0
- alysis_code/llm/provider_limits.py +525 -0
- alysis_code/llm/request_plan.py +389 -0
- alysis_code/llm/request_shape.py +238 -0
- alysis_code/llm/streaming.py +108 -0
- alysis_code/llm/temperature_compat.py +78 -0
- alysis_code/llm/types.py +195 -0
- alysis_code/llm/usage_normalization.py +222 -0
- alysis_code/llm_error_display.py +315 -0
- alysis_code/logging_redaction.py +326 -0
- alysis_code/managed_host_deadline.py +196 -0
- alysis_code/mcp/README.md +33 -0
- alysis_code/mcp/__init__.py +24 -0
- alysis_code/mcp/client.py +1137 -0
- alysis_code/mcp/config.py +597 -0
- alysis_code/mcp/errors.py +113 -0
- alysis_code/mcp/forge_scope.py +154 -0
- alysis_code/mcp/jsonrpc.py +214 -0
- alysis_code/mcp/manager.py +2308 -0
- alysis_code/mcp/models.py +666 -0
- alysis_code/mcp/oauth.py +972 -0
- alysis_code/mcp/oauth_runtime.py +310 -0
- alysis_code/mcp/oauth_store.py +276 -0
- alysis_code/mcp/prompts.py +329 -0
- alysis_code/mcp/resources.py +295 -0
- alysis_code/mcp/roots.py +106 -0
- alysis_code/mcp/server_requests.py +75 -0
- alysis_code/mcp/token_store.py +859 -0
- alysis_code/mcp/transport_http.py +1338 -0
- alysis_code/mcp/transport_stdio.py +1267 -0
- alysis_code/mcp/untrusted_content.py +119 -0
- alysis_code/merge_conflict_reviewer.py +729 -0
- alysis_code/model_catalog/__init__.py +1 -0
- alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
- alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
- alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
- alysis_code/model_metadata_policy.py +223 -0
- alysis_code/model_metadata_utils.py +103 -0
- alysis_code/model_registry.py +1420 -0
- alysis_code/model_router.py +147 -0
- alysis_code/permission_policy.py +1016 -0
- alysis_code/personas.py +451 -0
- alysis_code/pipeline_facts.py +233 -0
- alysis_code/plan_assistant.py +4763 -0
- alysis_code/plan_mode.py +393 -0
- alysis_code/plan_reconciliation.py +1228 -0
- alysis_code/plan_repair.py +652 -0
- alysis_code/plan_validation.py +1099 -0
- alysis_code/planning_constraints.py +904 -0
- alysis_code/policy.py +95 -0
- alysis_code/preview_server.py +457 -0
- alysis_code/process_reaping.py +566 -0
- alysis_code/profile_presets.py +1834 -0
- alysis_code/profiles.py +666 -0
- alysis_code/provider_auth/__init__.py +29 -0
- alysis_code/provider_auth/base.py +99 -0
- alysis_code/provider_auth/openai_codex.py +951 -0
- alysis_code/provider_auth/registry.py +76 -0
- alysis_code/provider_auth/store.py +125 -0
- alysis_code/provider_diagnostics.py +1209 -0
- alysis_code/provider_model_catalog.py +685 -0
- alysis_code/provider_telemetry.py +1699 -0
- alysis_code/provider_url.py +75 -0
- alysis_code/reasoning_contracts.py +911 -0
- alysis_code/remote_sync.py +350 -0
- alysis_code/replanning.py +1195 -0
- alysis_code/repo_scan.py +1152 -0
- alysis_code/request_estimation.py +296 -0
- alysis_code/review_gate.py +617 -0
- alysis_code/run_lock.py +1141 -0
- alysis_code/run_outcome.py +58 -0
- alysis_code/run_provenance.py +774 -0
- alysis_code/run_state.py +445 -0
- alysis_code/runtime_artifacts.py +116 -0
- alysis_code/runtime_context_features.py +78 -0
- alysis_code/runtime_kind.py +52 -0
- alysis_code/safety/__init__.py +11 -0
- alysis_code/safety/mcp_sanitize.py +29 -0
- alysis_code/safety/safe_http.py +297 -0
- alysis_code/safety/subagent_report.py +184 -0
- alysis_code/sandbox_doctor.py +682 -0
- alysis_code/sandbox_runner.py +1025 -0
- alysis_code/sandbox_settings.py +423 -0
- alysis_code/serialized_paths.py +355 -0
- alysis_code/server/__init__.py +3 -0
- alysis_code/server/app.py +367 -0
- alysis_code/server/auth.py +34 -0
- alysis_code/server/job_config.py +30 -0
- alysis_code/server/settings.py +215 -0
- alysis_code/server/store.py +193 -0
- alysis_code/server/worker_runner.py +657 -0
- alysis_code/service_persistence.py +355 -0
- alysis_code/session_artifacts.py +108 -0
- alysis_code/session_metrics.py +331 -0
- alysis_code/session_store.py +624 -0
- alysis_code/skills/README.md +34 -0
- alysis_code/skills/__init__.py +104 -0
- alysis_code/skills/conventions.py +84 -0
- alysis_code/skills/discovery.py +176 -0
- alysis_code/skills/eval_models.py +232 -0
- alysis_code/skills/eval_runner.py +372 -0
- alysis_code/skills/evals.py +1344 -0
- alysis_code/skills/install.py +293 -0
- alysis_code/skills/loader.py +118 -0
- alysis_code/skills/matching.py +103 -0
- alysis_code/skills/models.py +71 -0
- alysis_code/skills/paths.py +56 -0
- alysis_code/skills/prompting.py +500 -0
- alysis_code/skills/scaffold.py +142 -0
- alysis_code/skills/state.py +441 -0
- alysis_code/skills/transactions.py +125 -0
- alysis_code/skills/validation.py +304 -0
- alysis_code/step_budget.py +238 -0
- alysis_code/subagent_labels.py +49 -0
- alysis_code/subagents.py +1072 -0
- alysis_code/surface/__init__.py +80 -0
- alysis_code/surface/base.py +305 -0
- alysis_code/surface/console.py +387 -0
- alysis_code/surface/events.py +372 -0
- alysis_code/surface/hidden_surface.py +529 -0
- alysis_code/surface/noop_surface.py +219 -0
- alysis_code/surface/rich_surface.py +1555 -0
- alysis_code/surface/styles.py +67 -0
- alysis_code/surface/theme.py +455 -0
- alysis_code/surface/types.py +100 -0
- alysis_code/swarm_backend.py +926 -0
- alysis_code/swarm_orchestrator.py +4020 -0
- alysis_code/swarm_scheduler.py +441 -0
- alysis_code/swarm_trace.py +429 -0
- alysis_code/swarm_worker.py +2119 -0
- alysis_code/swarm_write_guard.py +348 -0
- alysis_code/task_dependencies.py +170 -0
- alysis_code/task_readiness.py +992 -0
- alysis_code/task_scope.py +2148 -0
- alysis_code/terminal_manager.py +762 -0
- alysis_code/terminal_ownership.py +460 -0
- alysis_code/text_normalization.py +30 -0
- alysis_code/token_budget.py +97 -0
- alysis_code/tools/README.md +34 -0
- alysis_code/tools/__init__.py +1 -0
- alysis_code/tools/artifacts.py +127 -0
- alysis_code/tools/availability.py +188 -0
- alysis_code/tools/fs.py +1456 -0
- alysis_code/tools/git.py +461 -0
- alysis_code/tools/history.py +229 -0
- alysis_code/tools/http_timeout.py +78 -0
- alysis_code/tools/image_generation.py +552 -0
- alysis_code/tools/registry.py +2936 -0
- alysis_code/tools/repo_map.py +476 -0
- alysis_code/tools/search.py +563 -0
- alysis_code/tools/shell.py +135 -0
- alysis_code/tools/symbols.py +1350 -0
- alysis_code/tools/test_discovery.py +643 -0
- alysis_code/tools/web.py +482 -0
- alysis_code/tools/web_search.py +2012 -0
- alysis_code/tools/web_search_dashscope.py +557 -0
- alysis_code/tools/web_search_ddgs.py +221 -0
- alysis_code/tools/web_search_provider_adapters.py +1429 -0
- alysis_code/tools/web_search_tavily.py +194 -0
- alysis_code/updates.py +933 -0
- alysis_code/usage_tracker.py +1990 -0
- alysis_code/verification_command_analysis.py +1004 -0
- alysis_code/verification_contract.py +574 -0
- alysis_code/verification_failure_summary.py +273 -0
- alysis_code/verification_repair.py +385 -0
- alysis_code/verify_gate.py +3129 -0
- alysis_code/web_research.py +1872 -0
- alysis_code/web_search_adapters.py +66 -0
- alysis_code/web_search_policy.py +27 -0
- alysis_code/workspace_binding.py +389 -0
- alysis_code/workspace_binding_ui.py +408 -0
- alysis_code/workspace_context.py +273 -0
- alysis_code/workspace_isolation.py +138 -0
- alysis_code/workspace_provisioning.py +455 -0
- alysis_code-0.13.0.dist-info/METADATA +507 -0
- alysis_code-0.13.0.dist-info/RECORD +458 -0
- alysis_code-0.13.0.dist-info/WHEEL +4 -0
- alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
- alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
- alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shlex
|
|
4
|
+
from dataclasses import dataclass, replace
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from pathlib import Path, PurePosixPath
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from ..branding import env_get
|
|
10
|
+
from ..pipeline_facts import pipeline_meaningful_stage
|
|
11
|
+
from ..verification_command_analysis import (
|
|
12
|
+
VerificationCommandEvidentiaryCapability,
|
|
13
|
+
analyze_verification_command,
|
|
14
|
+
)
|
|
15
|
+
from ..verification_contract import build_verification_command_specs
|
|
16
|
+
from ..verify_gate import assess_verification_command_execution
|
|
17
|
+
from .verification_commands import (
|
|
18
|
+
_canonicalize_verification_command_for_match,
|
|
19
|
+
_has_disallowed_shell_control_flow,
|
|
20
|
+
_marker_fallback_is_verification_attempt,
|
|
21
|
+
_matching_effective_verification_commands,
|
|
22
|
+
_normalize_shell_command_for_match,
|
|
23
|
+
_parse_verification_command_shape,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Recognized static-analysis families that inspect code without executing it.
|
|
27
|
+
# They do not qualify as post-edit *execution* evidence for the ordering rule
|
|
28
|
+
# (they are supplementary signals only); test runners and black-box checks do.
|
|
29
|
+
_STATIC_ONLY_EVIDENCE_FAMILIES = frozenset({"mypy", "ruff:check"})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _evidence_v2_enabled(cfg: Any | None) -> bool:
|
|
33
|
+
"""Kill-switch for the fact-based evidence classifier (evidence v2).
|
|
34
|
+
|
|
35
|
+
``ALYSIS_EVIDENCE_V2`` (off/0/false/no/disabled) wins over the config
|
|
36
|
+
value; default is on. Mirrors the route-arbitration kill-switch idiom.
|
|
37
|
+
"""
|
|
38
|
+
env_value = env_get("ALYSIS_EVIDENCE_V2")
|
|
39
|
+
if env_value is not None:
|
|
40
|
+
normalized = str(env_value).strip().lower()
|
|
41
|
+
if normalized in {"off", "0", "false", "no", "disabled"}:
|
|
42
|
+
return False
|
|
43
|
+
if normalized in {"on", "1", "true", "yes", "enabled"}:
|
|
44
|
+
return True
|
|
45
|
+
return bool(getattr(cfg, "evidence_v2_enabled", True))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def command_is_qualifying_execution_evidence(command: str) -> bool:
|
|
49
|
+
"""True when ``command`` executes code as real verification (not syntax-only).
|
|
50
|
+
|
|
51
|
+
Used by the ordering rule to decide whether an observed execution counts as
|
|
52
|
+
post-edit execution evidence. Judged from the analyzed command family, not a
|
|
53
|
+
string pattern: an assertive test/validation program qualifies; ``ast.parse``
|
|
54
|
+
/ ``py_compile`` (non-assertive) and static analyzers (mypy, ruff check) do
|
|
55
|
+
not. For a pipeline, the meaningful first-stage program is judged.
|
|
56
|
+
"""
|
|
57
|
+
target = pipeline_meaningful_stage(command) or command
|
|
58
|
+
analysis = analyze_verification_command(target, trusted=True)
|
|
59
|
+
if analysis.rejection_reason:
|
|
60
|
+
return False
|
|
61
|
+
if analysis.evidentiary_capability != VerificationCommandEvidentiaryCapability.ASSERTIVE:
|
|
62
|
+
return False
|
|
63
|
+
if analysis.command_family in _STATIC_ONLY_EVIDENCE_FAMILIES:
|
|
64
|
+
return False
|
|
65
|
+
# An inline interpreter snippet (e.g. ``python -c '<code>'``) only executes
|
|
66
|
+
# code as a real check when it asserts; ``ast.parse`` / import-only snippets
|
|
67
|
+
# are static and must not qualify as post-edit execution evidence.
|
|
68
|
+
parts = analysis.parts
|
|
69
|
+
if (
|
|
70
|
+
len(parts) >= 2
|
|
71
|
+
and parts[1] == "-c"
|
|
72
|
+
and analysis.command_family != "assertion:python_snippet"
|
|
73
|
+
):
|
|
74
|
+
return False
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class VerificationEvidenceCategory(StrEnum):
|
|
79
|
+
AUTHORITATIVE = "AUTHORITATIVE"
|
|
80
|
+
REPO_NATIVE = "REPO_NATIVE"
|
|
81
|
+
TASK_ACCEPTANCE = "TASK_ACCEPTANCE"
|
|
82
|
+
NOT_VERIFICATION = "NOT_VERIFICATION"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class VerificationEvidence:
|
|
87
|
+
category: VerificationEvidenceCategory
|
|
88
|
+
normalized_command: str
|
|
89
|
+
matched_command: str | None = None
|
|
90
|
+
real_execution: bool | None = None
|
|
91
|
+
allowed_to_satisfy_contract: bool = False
|
|
92
|
+
reason: str = ""
|
|
93
|
+
covered_verification_commands: tuple[str, ...] = ()
|
|
94
|
+
supplemental_only: bool = False
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def evidence_verdict(self) -> str:
|
|
98
|
+
"""Machine-readable verdict for telemetry.
|
|
99
|
+
|
|
100
|
+
``pass`` — executed and accepted as satisfying verification;
|
|
101
|
+
``fail`` — executed but did not pass (or mutated a material path);
|
|
102
|
+
``unavailable`` — a pipeline whose per-stage status could not be observed;
|
|
103
|
+
``supplemental`` — self-authored, cannot independently confirm the spec;
|
|
104
|
+
``not_verification`` — the command is not a verification attempt.
|
|
105
|
+
"""
|
|
106
|
+
if self.category == VerificationEvidenceCategory.NOT_VERIFICATION:
|
|
107
|
+
if self.reason == "pipeline_stage_status_unavailable":
|
|
108
|
+
return "unavailable"
|
|
109
|
+
return "not_verification"
|
|
110
|
+
if self.supplemental_only:
|
|
111
|
+
return "supplemental"
|
|
112
|
+
return "pass" if self.allowed_to_satisfy_contract else "fail"
|
|
113
|
+
|
|
114
|
+
def as_payload(self) -> dict[str, object]:
|
|
115
|
+
return {
|
|
116
|
+
"evidence_category": self.category.value,
|
|
117
|
+
"evidence_verdict": self.evidence_verdict,
|
|
118
|
+
"normalized_command": self.normalized_command,
|
|
119
|
+
"matched_command": self.matched_command,
|
|
120
|
+
"real_execution": self.real_execution,
|
|
121
|
+
"allowed_to_satisfy_contract": self.allowed_to_satisfy_contract,
|
|
122
|
+
"reason": self.reason,
|
|
123
|
+
"covered_verification_commands": list(self.covered_verification_commands),
|
|
124
|
+
"supplemental_only": self.supplemental_only,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
_INTERPRETER_HEADS = {
|
|
129
|
+
"python",
|
|
130
|
+
"python3",
|
|
131
|
+
"py",
|
|
132
|
+
"node",
|
|
133
|
+
"ruby",
|
|
134
|
+
"rscript",
|
|
135
|
+
"r",
|
|
136
|
+
"bash",
|
|
137
|
+
"sh",
|
|
138
|
+
"zsh",
|
|
139
|
+
}
|
|
140
|
+
_OBSERVATION_HEADS = {
|
|
141
|
+
"cat",
|
|
142
|
+
"echo",
|
|
143
|
+
"find",
|
|
144
|
+
"grep",
|
|
145
|
+
"head",
|
|
146
|
+
"less",
|
|
147
|
+
"ls",
|
|
148
|
+
"more",
|
|
149
|
+
"printf",
|
|
150
|
+
"pwd",
|
|
151
|
+
"tail",
|
|
152
|
+
"type",
|
|
153
|
+
"wc",
|
|
154
|
+
"which",
|
|
155
|
+
}
|
|
156
|
+
_META_OPTIONS = {"--help", "-h", "--version", "-v", "version", "help"}
|
|
157
|
+
_VALIDATION_NAME_MARKERS = (
|
|
158
|
+
"accept",
|
|
159
|
+
"acceptance",
|
|
160
|
+
"check",
|
|
161
|
+
"smoke",
|
|
162
|
+
"test",
|
|
163
|
+
"validate",
|
|
164
|
+
"validation",
|
|
165
|
+
"verify",
|
|
166
|
+
)
|
|
167
|
+
_VALIDATION_DIR_MARKERS = {
|
|
168
|
+
"acceptance",
|
|
169
|
+
"check",
|
|
170
|
+
"checks",
|
|
171
|
+
"script",
|
|
172
|
+
"scripts",
|
|
173
|
+
"smoke",
|
|
174
|
+
"test",
|
|
175
|
+
"tests",
|
|
176
|
+
"validation",
|
|
177
|
+
"verify",
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _execution_allows_acceptance(
|
|
182
|
+
*,
|
|
183
|
+
exit_code: int | None,
|
|
184
|
+
real_execution: bool | None,
|
|
185
|
+
) -> bool:
|
|
186
|
+
if real_execution is not True:
|
|
187
|
+
return False
|
|
188
|
+
if exit_code is not None and exit_code != 0:
|
|
189
|
+
return False
|
|
190
|
+
return True
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _invalid_contract_rejection_reason(
|
|
194
|
+
*,
|
|
195
|
+
matches: set[str],
|
|
196
|
+
known_verification_commands: list[str],
|
|
197
|
+
authoritative: bool,
|
|
198
|
+
) -> str:
|
|
199
|
+
if not matches:
|
|
200
|
+
return ""
|
|
201
|
+
source = (
|
|
202
|
+
"environment.authoritative_verification_commands"
|
|
203
|
+
if authoritative
|
|
204
|
+
else "task_refinement.explicit_user_command"
|
|
205
|
+
)
|
|
206
|
+
contract_type = "authoritative_override" if authoritative else "task_acceptance"
|
|
207
|
+
normalized_matches = {_normalize_shell_command_for_match(command) for command in matches}
|
|
208
|
+
for spec in build_verification_command_specs(
|
|
209
|
+
tuple(known_verification_commands),
|
|
210
|
+
source=source,
|
|
211
|
+
contract_type=contract_type,
|
|
212
|
+
):
|
|
213
|
+
if _normalize_shell_command_for_match(spec.original_text) not in normalized_matches:
|
|
214
|
+
continue
|
|
215
|
+
if spec.validation_status.value == "INVALID":
|
|
216
|
+
return spec.rejection_reason or "invalid_verification_command"
|
|
217
|
+
return ""
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _contract_execution_allows_acceptance(
|
|
221
|
+
*,
|
|
222
|
+
exit_code: int | None,
|
|
223
|
+
real_execution: bool | None,
|
|
224
|
+
) -> bool:
|
|
225
|
+
if real_execution is not True:
|
|
226
|
+
return False
|
|
227
|
+
if exit_code is not None and exit_code != 0:
|
|
228
|
+
return False
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _command_parts(command: str) -> list[str]:
|
|
233
|
+
canonical = _canonicalize_verification_command_for_match(command)
|
|
234
|
+
if not canonical:
|
|
235
|
+
return []
|
|
236
|
+
try:
|
|
237
|
+
return shlex.split(canonical, posix=True)
|
|
238
|
+
except ValueError:
|
|
239
|
+
return []
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _path_key(path: str, *, root: Path | None = None) -> str:
|
|
243
|
+
cleaned = str(path or "").strip().replace("\\", "/")
|
|
244
|
+
while cleaned.startswith("./"):
|
|
245
|
+
cleaned = cleaned[2:]
|
|
246
|
+
if root is not None and cleaned:
|
|
247
|
+
try:
|
|
248
|
+
resolved = Path(cleaned).expanduser().resolve()
|
|
249
|
+
cleaned = resolved.relative_to(root.resolve()).as_posix()
|
|
250
|
+
except (OSError, ValueError):
|
|
251
|
+
pass
|
|
252
|
+
return cleaned.casefold()
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _is_repo_local_path(path: str) -> bool:
|
|
256
|
+
if not path or path.startswith("-"):
|
|
257
|
+
return False
|
|
258
|
+
pure = PurePosixPath(path.replace("\\", "/"))
|
|
259
|
+
return not pure.is_absolute() and ".." not in pure.parts
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _is_repo_local_executable_path(path: str) -> bool:
|
|
263
|
+
normalized = path.replace("\\", "/")
|
|
264
|
+
return _is_repo_local_path(path) and ("/" in normalized or normalized.startswith("."))
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _path_has_validation_signal(path: str) -> bool:
|
|
268
|
+
pure = PurePosixPath(path.replace("\\", "/"))
|
|
269
|
+
parts = tuple(part.casefold() for part in pure.parts if part not in {"", "."})
|
|
270
|
+
name = pure.stem.casefold()
|
|
271
|
+
if any(marker in name for marker in _VALIDATION_NAME_MARKERS):
|
|
272
|
+
return True
|
|
273
|
+
return bool(set(parts[:-1]) & _VALIDATION_DIR_MARKERS)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _interpreter_script_path(parts: list[str]) -> str | None:
|
|
277
|
+
if not parts:
|
|
278
|
+
return None
|
|
279
|
+
head = Path(parts[0]).name.casefold()
|
|
280
|
+
if head.endswith(".exe"):
|
|
281
|
+
head = head[:-4]
|
|
282
|
+
if head not in _INTERPRETER_HEADS:
|
|
283
|
+
return None
|
|
284
|
+
tail = parts[1:]
|
|
285
|
+
if not tail:
|
|
286
|
+
return None
|
|
287
|
+
if tail[0] in _META_OPTIONS:
|
|
288
|
+
return None
|
|
289
|
+
if head in {"python", "python3", "py"} and tail[0] == "-m":
|
|
290
|
+
return None
|
|
291
|
+
for item in tail:
|
|
292
|
+
if item == "--":
|
|
293
|
+
continue
|
|
294
|
+
if _is_repo_local_path(item):
|
|
295
|
+
return item
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _diff_or_cmp_has_real_operands(parts: list[str]) -> bool:
|
|
300
|
+
if not parts:
|
|
301
|
+
return False
|
|
302
|
+
head = Path(parts[0]).name.casefold()
|
|
303
|
+
if head not in {"cmp", "diff"}:
|
|
304
|
+
return False
|
|
305
|
+
operands = [part for part in parts[1:] if part != "--" and not part.startswith("-")]
|
|
306
|
+
return len(operands) >= 2 and all(_is_repo_local_path(item) for item in operands[:2])
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _is_known_non_executing_verification_form(parts: list[str]) -> bool:
|
|
310
|
+
lowered = [part.casefold() for part in parts]
|
|
311
|
+
if not lowered:
|
|
312
|
+
return True
|
|
313
|
+
if any(part in {"--help", "-h", "--version"} for part in lowered):
|
|
314
|
+
return True
|
|
315
|
+
if lowered[0] in {"pytest", "py.test"}:
|
|
316
|
+
return any(part in {"--collect-only", "--co", "--setup-plan"} for part in lowered[1:])
|
|
317
|
+
if len(lowered) >= 3 and lowered[0] in {"python", "python3", "py"} and lowered[1] == "-m":
|
|
318
|
+
if lowered[2] == "pytest":
|
|
319
|
+
return any(part in {"--collect-only", "--co", "--setup-plan"} for part in lowered[3:])
|
|
320
|
+
if lowered[:2] == ["go", "test"]:
|
|
321
|
+
if any(part in {"-c", "-list"} or part.startswith("-list=") for part in lowered[2:]):
|
|
322
|
+
return True
|
|
323
|
+
for index, part in enumerate(lowered[2:], start=2):
|
|
324
|
+
if part == "-run" and index + 1 < len(lowered) and lowered[index + 1] == "^$":
|
|
325
|
+
return True
|
|
326
|
+
if part == "-run=^$":
|
|
327
|
+
return True
|
|
328
|
+
if lowered[:2] == ["cargo", "test"]:
|
|
329
|
+
return any(part in {"--no-run", "--list"} for part in lowered[2:])
|
|
330
|
+
if lowered[:2] == ["ruff", "check"]:
|
|
331
|
+
return any(part in {"--fix", "--fix-only"} for part in lowered[2:])
|
|
332
|
+
if lowered[0] == "mypy":
|
|
333
|
+
return "--install-types" in lowered[1:]
|
|
334
|
+
return False
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _task_specific_reason(
|
|
338
|
+
*,
|
|
339
|
+
parts: list[str],
|
|
340
|
+
changed_paths: set[str],
|
|
341
|
+
root: Path | None,
|
|
342
|
+
) -> str | None:
|
|
343
|
+
if not parts:
|
|
344
|
+
return None
|
|
345
|
+
head = Path(parts[0]).name.casefold()
|
|
346
|
+
if head.endswith(".exe"):
|
|
347
|
+
head = head[:-4]
|
|
348
|
+
if head in _OBSERVATION_HEADS:
|
|
349
|
+
return None
|
|
350
|
+
if any(part.casefold() in _META_OPTIONS for part in parts[1:]):
|
|
351
|
+
return None
|
|
352
|
+
if _diff_or_cmp_has_real_operands(parts):
|
|
353
|
+
return "real_output_comparison"
|
|
354
|
+
|
|
355
|
+
script_path = _interpreter_script_path(parts)
|
|
356
|
+
if script_path:
|
|
357
|
+
script_key = _path_key(script_path, root=root)
|
|
358
|
+
changed_keys = {_path_key(item, root=root) for item in changed_paths}
|
|
359
|
+
if script_key in changed_keys:
|
|
360
|
+
return "changed_repo_local_script_execution"
|
|
361
|
+
if _path_has_validation_signal(script_path):
|
|
362
|
+
return "repo_local_validation_script"
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
executable = parts[0]
|
|
366
|
+
if _is_repo_local_executable_path(executable) and _path_has_validation_signal(executable):
|
|
367
|
+
return "repo_local_validation_executable"
|
|
368
|
+
return None
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def classify_verification_evidence(
|
|
372
|
+
command: str,
|
|
373
|
+
*,
|
|
374
|
+
known_verification_commands: list[str] | tuple[str, ...] | None = None,
|
|
375
|
+
authoritative: bool = False,
|
|
376
|
+
changed_paths: set[str] | list[str] | tuple[str, ...] | None = None,
|
|
377
|
+
material_touched_paths: set[str] | list[str] | tuple[str, ...] | None = None,
|
|
378
|
+
exit_code: int | None = None,
|
|
379
|
+
output: str = "",
|
|
380
|
+
real_execution: bool | None = None,
|
|
381
|
+
root: Path | None = None,
|
|
382
|
+
stage_status: list[int] | tuple[int, ...] | None = None,
|
|
383
|
+
evidence_v2: bool = True,
|
|
384
|
+
) -> VerificationEvidence:
|
|
385
|
+
"""Classify an observed command as verification evidence.
|
|
386
|
+
|
|
387
|
+
Evidence v2 (default) judges observed *execution facts*: for a shell
|
|
388
|
+
pipeline the meaningful program is the first stage and its per-stage exit
|
|
389
|
+
status (``stage_status``, captured via PIPESTATUS) is the ground truth, so a
|
|
390
|
+
piped ``pytest ... | tail`` counts as passing/failing evidence per the
|
|
391
|
+
pipeline's real first-stage code rather than being rejected for containing a
|
|
392
|
+
``|``. When per-stage status is unavailable the pipeline is treated as
|
|
393
|
+
unverified (``pipeline_stage_status_unavailable``) so it can never be
|
|
394
|
+
silently accepted. With ``evidence_v2`` off, the legacy string-shape
|
|
395
|
+
classifier runs unchanged.
|
|
396
|
+
"""
|
|
397
|
+
if evidence_v2:
|
|
398
|
+
first_stage = pipeline_meaningful_stage(command)
|
|
399
|
+
if first_stage is not None:
|
|
400
|
+
return _classify_pipeline_evidence(
|
|
401
|
+
command=command,
|
|
402
|
+
first_stage=first_stage,
|
|
403
|
+
stage_status=list(stage_status) if stage_status is not None else None,
|
|
404
|
+
known_verification_commands=known_verification_commands,
|
|
405
|
+
authoritative=authoritative,
|
|
406
|
+
changed_paths=changed_paths,
|
|
407
|
+
material_touched_paths=material_touched_paths,
|
|
408
|
+
output=output,
|
|
409
|
+
root=root,
|
|
410
|
+
)
|
|
411
|
+
return _classify_verification_evidence_legacy(
|
|
412
|
+
command,
|
|
413
|
+
known_verification_commands=known_verification_commands,
|
|
414
|
+
authoritative=authoritative,
|
|
415
|
+
changed_paths=changed_paths,
|
|
416
|
+
material_touched_paths=material_touched_paths,
|
|
417
|
+
exit_code=exit_code,
|
|
418
|
+
output=output,
|
|
419
|
+
real_execution=real_execution,
|
|
420
|
+
root=root,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _classify_pipeline_evidence(
|
|
425
|
+
*,
|
|
426
|
+
command: str,
|
|
427
|
+
first_stage: str,
|
|
428
|
+
stage_status: list[int] | None,
|
|
429
|
+
known_verification_commands: list[str] | tuple[str, ...] | None,
|
|
430
|
+
authoritative: bool,
|
|
431
|
+
changed_paths: set[str] | list[str] | tuple[str, ...] | None,
|
|
432
|
+
material_touched_paths: set[str] | list[str] | tuple[str, ...] | None,
|
|
433
|
+
output: str,
|
|
434
|
+
root: Path | None,
|
|
435
|
+
) -> VerificationEvidence:
|
|
436
|
+
normalized = _normalize_shell_command_for_match(command)
|
|
437
|
+
if not stage_status:
|
|
438
|
+
# No observed per-stage status and no ground truth obtained: the
|
|
439
|
+
# pipeline's real first-stage outcome is unknown, so it is not evidence.
|
|
440
|
+
return VerificationEvidence(
|
|
441
|
+
category=VerificationEvidenceCategory.NOT_VERIFICATION,
|
|
442
|
+
normalized_command=normalized,
|
|
443
|
+
real_execution=None,
|
|
444
|
+
reason="pipeline_stage_status_unavailable",
|
|
445
|
+
)
|
|
446
|
+
first_stage_exit = stage_status[0]
|
|
447
|
+
evidence = _classify_verification_evidence_legacy(
|
|
448
|
+
first_stage,
|
|
449
|
+
known_verification_commands=known_verification_commands,
|
|
450
|
+
authoritative=authoritative,
|
|
451
|
+
changed_paths=changed_paths,
|
|
452
|
+
material_touched_paths=material_touched_paths,
|
|
453
|
+
exit_code=first_stage_exit,
|
|
454
|
+
output=output,
|
|
455
|
+
real_execution=None,
|
|
456
|
+
root=root,
|
|
457
|
+
)
|
|
458
|
+
# Keep the observed (piped) command as the normalized command for telemetry;
|
|
459
|
+
# the covered contract commands already reflect the matched contract text.
|
|
460
|
+
return replace(evidence, normalized_command=normalized)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def _classify_verification_evidence_legacy(
|
|
464
|
+
command: str,
|
|
465
|
+
*,
|
|
466
|
+
known_verification_commands: list[str] | tuple[str, ...] | None = None,
|
|
467
|
+
authoritative: bool = False,
|
|
468
|
+
changed_paths: set[str] | list[str] | tuple[str, ...] | None = None,
|
|
469
|
+
material_touched_paths: set[str] | list[str] | tuple[str, ...] | None = None,
|
|
470
|
+
exit_code: int | None = None,
|
|
471
|
+
output: str = "",
|
|
472
|
+
real_execution: bool | None = None,
|
|
473
|
+
root: Path | None = None,
|
|
474
|
+
) -> VerificationEvidence:
|
|
475
|
+
normalized = _normalize_shell_command_for_match(command)
|
|
476
|
+
if not normalized:
|
|
477
|
+
return VerificationEvidence(
|
|
478
|
+
category=VerificationEvidenceCategory.NOT_VERIFICATION,
|
|
479
|
+
normalized_command="",
|
|
480
|
+
real_execution=real_execution,
|
|
481
|
+
reason="empty_command",
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
if real_execution is None and exit_code is not None:
|
|
485
|
+
assessment = assess_verification_command_execution(
|
|
486
|
+
command=command,
|
|
487
|
+
exit_code=exit_code,
|
|
488
|
+
output=output,
|
|
489
|
+
)
|
|
490
|
+
real_execution = assessment.real_execution
|
|
491
|
+
|
|
492
|
+
known = [str(item) for item in (known_verification_commands or []) if str(item).strip()]
|
|
493
|
+
analysis = analyze_verification_command(
|
|
494
|
+
command,
|
|
495
|
+
trusted=authoritative or bool(known),
|
|
496
|
+
)
|
|
497
|
+
material_touched = tuple(
|
|
498
|
+
sorted(str(item) for item in (material_touched_paths or []) if str(item).strip())
|
|
499
|
+
)
|
|
500
|
+
execution_ok = _execution_allows_acceptance(
|
|
501
|
+
exit_code=exit_code,
|
|
502
|
+
real_execution=real_execution,
|
|
503
|
+
)
|
|
504
|
+
mutation_reason = "mutated_material_paths" if material_touched else ""
|
|
505
|
+
trusted_shell_matches = _matching_trusted_shell_expressions(
|
|
506
|
+
observed_command=command,
|
|
507
|
+
known_verification_commands=known,
|
|
508
|
+
authoritative=authoritative,
|
|
509
|
+
)
|
|
510
|
+
if trusted_shell_matches:
|
|
511
|
+
contract_execution_ok = _contract_execution_allows_acceptance(
|
|
512
|
+
exit_code=exit_code,
|
|
513
|
+
real_execution=real_execution,
|
|
514
|
+
)
|
|
515
|
+
capability_ok = (
|
|
516
|
+
analysis.evidentiary_capability == VerificationCommandEvidentiaryCapability.ASSERTIVE
|
|
517
|
+
and not analysis.rejection_reason
|
|
518
|
+
)
|
|
519
|
+
allowed = contract_execution_ok and capability_ok and not material_touched
|
|
520
|
+
category = (
|
|
521
|
+
VerificationEvidenceCategory.AUTHORITATIVE
|
|
522
|
+
if authoritative
|
|
523
|
+
else VerificationEvidenceCategory.REPO_NATIVE
|
|
524
|
+
)
|
|
525
|
+
if not capability_ok:
|
|
526
|
+
reason = (
|
|
527
|
+
analysis.rejection_reason
|
|
528
|
+
or analysis.inconclusive_reason
|
|
529
|
+
or "unknown_verification_capability"
|
|
530
|
+
)
|
|
531
|
+
elif not contract_execution_ok:
|
|
532
|
+
reason = "non_executing_or_failed_contract_command"
|
|
533
|
+
elif mutation_reason:
|
|
534
|
+
reason = mutation_reason
|
|
535
|
+
else:
|
|
536
|
+
reason = "matched_authoritative_contract" if authoritative else "matched_contract"
|
|
537
|
+
return VerificationEvidence(
|
|
538
|
+
category=category,
|
|
539
|
+
normalized_command=normalized,
|
|
540
|
+
matched_command=sorted(trusted_shell_matches)[0],
|
|
541
|
+
real_execution=real_execution,
|
|
542
|
+
allowed_to_satisfy_contract=allowed,
|
|
543
|
+
reason=reason,
|
|
544
|
+
covered_verification_commands=tuple(sorted(trusted_shell_matches)),
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
if _has_disallowed_shell_control_flow(command):
|
|
548
|
+
return VerificationEvidence(
|
|
549
|
+
category=VerificationEvidenceCategory.NOT_VERIFICATION,
|
|
550
|
+
normalized_command=normalized,
|
|
551
|
+
real_execution=real_execution,
|
|
552
|
+
reason=analysis.rejection_reason or "disallowed_shell_control_flow",
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
matches = _matching_effective_verification_commands(
|
|
556
|
+
observed_command=normalized,
|
|
557
|
+
effective_verification_commands=known,
|
|
558
|
+
)
|
|
559
|
+
if matches:
|
|
560
|
+
invalid_contract_reason = _invalid_contract_rejection_reason(
|
|
561
|
+
matches=matches,
|
|
562
|
+
known_verification_commands=known,
|
|
563
|
+
authoritative=authoritative,
|
|
564
|
+
)
|
|
565
|
+
contract_execution_ok = _contract_execution_allows_acceptance(
|
|
566
|
+
exit_code=exit_code,
|
|
567
|
+
real_execution=real_execution,
|
|
568
|
+
)
|
|
569
|
+
capability_ok = (
|
|
570
|
+
analysis.evidentiary_capability == VerificationCommandEvidentiaryCapability.ASSERTIVE
|
|
571
|
+
and not analysis.rejection_reason
|
|
572
|
+
)
|
|
573
|
+
allowed = (
|
|
574
|
+
not invalid_contract_reason
|
|
575
|
+
and contract_execution_ok
|
|
576
|
+
and capability_ok
|
|
577
|
+
and not material_touched
|
|
578
|
+
)
|
|
579
|
+
category = (
|
|
580
|
+
VerificationEvidenceCategory.AUTHORITATIVE
|
|
581
|
+
if authoritative
|
|
582
|
+
else VerificationEvidenceCategory.REPO_NATIVE
|
|
583
|
+
)
|
|
584
|
+
if invalid_contract_reason:
|
|
585
|
+
reason = invalid_contract_reason
|
|
586
|
+
elif not capability_ok:
|
|
587
|
+
reason = (
|
|
588
|
+
analysis.rejection_reason
|
|
589
|
+
or analysis.inconclusive_reason
|
|
590
|
+
or "unknown_verification_capability"
|
|
591
|
+
)
|
|
592
|
+
elif not contract_execution_ok:
|
|
593
|
+
reason = "non_executing_or_failed_contract_command"
|
|
594
|
+
elif mutation_reason:
|
|
595
|
+
reason = mutation_reason
|
|
596
|
+
else:
|
|
597
|
+
reason = "matched_authoritative_contract" if authoritative else "matched_contract"
|
|
598
|
+
return VerificationEvidence(
|
|
599
|
+
category=category,
|
|
600
|
+
normalized_command=normalized,
|
|
601
|
+
matched_command=sorted(matches)[0],
|
|
602
|
+
real_execution=real_execution,
|
|
603
|
+
allowed_to_satisfy_contract=allowed,
|
|
604
|
+
reason=reason,
|
|
605
|
+
covered_verification_commands=tuple(sorted(matches)),
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
parts = _command_parts(normalized)
|
|
609
|
+
task_reason = _task_specific_reason(
|
|
610
|
+
parts=parts,
|
|
611
|
+
changed_paths={str(item) for item in (changed_paths or []) if str(item).strip()},
|
|
612
|
+
root=root,
|
|
613
|
+
)
|
|
614
|
+
if known:
|
|
615
|
+
if task_reason:
|
|
616
|
+
return VerificationEvidence(
|
|
617
|
+
category=VerificationEvidenceCategory.TASK_ACCEPTANCE,
|
|
618
|
+
normalized_command=normalized,
|
|
619
|
+
real_execution=real_execution,
|
|
620
|
+
allowed_to_satisfy_contract=False,
|
|
621
|
+
reason="supplemental_only_contract_exists",
|
|
622
|
+
supplemental_only=True,
|
|
623
|
+
)
|
|
624
|
+
return VerificationEvidence(
|
|
625
|
+
category=VerificationEvidenceCategory.NOT_VERIFICATION,
|
|
626
|
+
normalized_command=normalized,
|
|
627
|
+
real_execution=real_execution,
|
|
628
|
+
reason="does_not_match_effective_contract",
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
if task_reason:
|
|
632
|
+
if (
|
|
633
|
+
analysis.evidentiary_capability
|
|
634
|
+
== VerificationCommandEvidentiaryCapability.NON_ASSERTIVE
|
|
635
|
+
or analysis.rejection_reason
|
|
636
|
+
):
|
|
637
|
+
return VerificationEvidence(
|
|
638
|
+
category=VerificationEvidenceCategory.NOT_VERIFICATION,
|
|
639
|
+
normalized_command=normalized,
|
|
640
|
+
real_execution=real_execution,
|
|
641
|
+
reason=analysis.rejection_reason
|
|
642
|
+
or analysis.capability_reason
|
|
643
|
+
or "non_assertive_verifier",
|
|
644
|
+
)
|
|
645
|
+
allowed = execution_ok and not material_touched
|
|
646
|
+
reason = task_reason if allowed else mutation_reason or "non_executing_or_failed_task_check"
|
|
647
|
+
return VerificationEvidence(
|
|
648
|
+
category=VerificationEvidenceCategory.TASK_ACCEPTANCE,
|
|
649
|
+
normalized_command=normalized,
|
|
650
|
+
real_execution=real_execution,
|
|
651
|
+
allowed_to_satisfy_contract=allowed,
|
|
652
|
+
reason=reason,
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
if (
|
|
656
|
+
_parse_verification_command_shape(normalized) is not None
|
|
657
|
+
and analysis.evidentiary_capability == VerificationCommandEvidentiaryCapability.ASSERTIVE
|
|
658
|
+
and not analysis.rejection_reason
|
|
659
|
+
):
|
|
660
|
+
allowed = execution_ok and not material_touched
|
|
661
|
+
reason = (
|
|
662
|
+
"repo_native_command"
|
|
663
|
+
if allowed
|
|
664
|
+
else mutation_reason or "non_executing_or_failed_repo_native_command"
|
|
665
|
+
)
|
|
666
|
+
return VerificationEvidence(
|
|
667
|
+
category=VerificationEvidenceCategory.REPO_NATIVE,
|
|
668
|
+
normalized_command=normalized,
|
|
669
|
+
real_execution=real_execution,
|
|
670
|
+
allowed_to_satisfy_contract=allowed,
|
|
671
|
+
reason=reason,
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
if _marker_fallback_is_verification_attempt(
|
|
675
|
+
normalized
|
|
676
|
+
) and not _is_known_non_executing_verification_form(parts):
|
|
677
|
+
if (
|
|
678
|
+
analysis.evidentiary_capability != VerificationCommandEvidentiaryCapability.ASSERTIVE
|
|
679
|
+
or analysis.rejection_reason
|
|
680
|
+
):
|
|
681
|
+
return VerificationEvidence(
|
|
682
|
+
category=VerificationEvidenceCategory.NOT_VERIFICATION,
|
|
683
|
+
normalized_command=normalized,
|
|
684
|
+
real_execution=real_execution,
|
|
685
|
+
reason=analysis.rejection_reason
|
|
686
|
+
or analysis.inconclusive_reason
|
|
687
|
+
or "unknown_verification_capability",
|
|
688
|
+
)
|
|
689
|
+
allowed = execution_ok and not material_touched
|
|
690
|
+
reason = (
|
|
691
|
+
"repo_native_command"
|
|
692
|
+
if allowed
|
|
693
|
+
else mutation_reason or "non_executing_or_failed_repo_native_command"
|
|
694
|
+
)
|
|
695
|
+
return VerificationEvidence(
|
|
696
|
+
category=VerificationEvidenceCategory.REPO_NATIVE,
|
|
697
|
+
normalized_command=normalized,
|
|
698
|
+
real_execution=real_execution,
|
|
699
|
+
allowed_to_satisfy_contract=allowed,
|
|
700
|
+
reason=reason,
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
return VerificationEvidence(
|
|
704
|
+
category=VerificationEvidenceCategory.NOT_VERIFICATION,
|
|
705
|
+
normalized_command=normalized,
|
|
706
|
+
real_execution=real_execution,
|
|
707
|
+
reason="no_verification_signal",
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def _matching_trusted_shell_expressions(
|
|
712
|
+
*,
|
|
713
|
+
observed_command: str,
|
|
714
|
+
known_verification_commands: list[str],
|
|
715
|
+
authoritative: bool,
|
|
716
|
+
) -> set[str]:
|
|
717
|
+
if not known_verification_commands:
|
|
718
|
+
return set()
|
|
719
|
+
source = (
|
|
720
|
+
"environment.authoritative_verification_commands"
|
|
721
|
+
if authoritative
|
|
722
|
+
else "task_refinement.explicit_user_command"
|
|
723
|
+
)
|
|
724
|
+
contract_type = "authoritative_override" if authoritative else "task_acceptance"
|
|
725
|
+
observed = _normalize_shell_command_for_match(observed_command)
|
|
726
|
+
out: set[str] = set()
|
|
727
|
+
for spec in build_verification_command_specs(
|
|
728
|
+
tuple(known_verification_commands),
|
|
729
|
+
source=source,
|
|
730
|
+
contract_type=contract_type,
|
|
731
|
+
):
|
|
732
|
+
if (
|
|
733
|
+
spec.execution_mode.value == "TRUSTED_SHELL_EXPRESSION"
|
|
734
|
+
and spec.validation_status.value == "VALID"
|
|
735
|
+
and observed == _normalize_shell_command_for_match(spec.original_text)
|
|
736
|
+
):
|
|
737
|
+
out.add(spec.original_text)
|
|
738
|
+
return out
|