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,512 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import shlex
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ..config import (
|
|
9
|
+
strip_verify_runner_prefix,
|
|
10
|
+
)
|
|
11
|
+
from ..verification_command_analysis import (
|
|
12
|
+
VerificationCommandEvidentiaryCapability,
|
|
13
|
+
analyze_verification_command,
|
|
14
|
+
)
|
|
15
|
+
from .prompt_context import _normalized_verify_commands
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _normalize_shell_command_for_match(raw: str) -> str:
|
|
19
|
+
return " ".join(str(raw or "").casefold().split())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_VERIFICATION_ENV_ASSIGNMENT_RE = re.compile(r"^[a-z_][a-z0-9_]*=.*$")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_DISALLOWED_VERIFICATION_SHELL_TOKENS = {"||", "&&", ";", "|", "&"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_NON_VERIFICATION_META_OPTIONS = {"--help", "-h", "--version"}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_PYTEST_NON_VERIFICATION_OPTIONS = {"--fixtures", "--markers", "--collect-only", "--co"}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_PYTEST_NON_EXECUTING_OPTIONS = {"--setup-plan"}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_CARGO_TEST_NON_EXECUTING_OPTIONS = {"--no-run", "--list"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
_GO_TEST_NON_EXECUTING_OPTIONS = {"-c", "-list"}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_RUFF_CHECK_NON_EXECUTING_OPTIONS = {"--fix", "--fix-only"}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_MYPY_NON_EXECUTING_OPTIONS = {"--install-types"}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
_PYTEST_REPORTER_OPTIONS = {"-q", "--quiet", "--verbose"}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class VerificationCommandShape:
|
|
54
|
+
family: str
|
|
55
|
+
args: tuple[str, ...]
|
|
56
|
+
options: tuple[str, ...]
|
|
57
|
+
positionals: tuple[str, ...]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _command_options_include(options: tuple[str, ...], flag: str) -> bool:
|
|
61
|
+
return any(option == flag or option.startswith(f"{flag}=") for option in options)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _verification_shape_is_real_execution_mode(shape: VerificationCommandShape) -> bool:
|
|
65
|
+
if shape.family == "pytest":
|
|
66
|
+
return not any(
|
|
67
|
+
_command_options_include(shape.options, flag) for flag in _PYTEST_NON_EXECUTING_OPTIONS
|
|
68
|
+
)
|
|
69
|
+
if shape.family == "cargo:test":
|
|
70
|
+
return not any(
|
|
71
|
+
_command_options_include(shape.options, flag)
|
|
72
|
+
for flag in _CARGO_TEST_NON_EXECUTING_OPTIONS
|
|
73
|
+
)
|
|
74
|
+
if shape.family == "go:test":
|
|
75
|
+
if any(
|
|
76
|
+
_command_options_include(shape.options, flag) for flag in _GO_TEST_NON_EXECUTING_OPTIONS
|
|
77
|
+
):
|
|
78
|
+
return False
|
|
79
|
+
for idx, arg in enumerate(shape.args):
|
|
80
|
+
if arg == "-run":
|
|
81
|
+
if idx + 1 >= len(shape.args):
|
|
82
|
+
return False
|
|
83
|
+
if shape.args[idx + 1] == "^$":
|
|
84
|
+
return False
|
|
85
|
+
elif arg.startswith("-run=") and arg.partition("=")[2] == "^$":
|
|
86
|
+
return False
|
|
87
|
+
return True
|
|
88
|
+
if shape.family == "ruff:check":
|
|
89
|
+
return not any(
|
|
90
|
+
_command_options_include(shape.options, flag)
|
|
91
|
+
for flag in _RUFF_CHECK_NON_EXECUTING_OPTIONS
|
|
92
|
+
)
|
|
93
|
+
if shape.family == "mypy":
|
|
94
|
+
return not any(
|
|
95
|
+
_command_options_include(shape.options, flag) for flag in _MYPY_NON_EXECUTING_OPTIONS
|
|
96
|
+
)
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _looks_like_env_assignment_token(part: str) -> bool:
|
|
101
|
+
return bool(_VERIFICATION_ENV_ASSIGNMENT_RE.match(part))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _strip_verification_env_prefix(parts: list[str]) -> list[str] | None:
|
|
105
|
+
if not parts:
|
|
106
|
+
return None
|
|
107
|
+
out = list(parts)
|
|
108
|
+
if out[0] == "env":
|
|
109
|
+
out = out[1:]
|
|
110
|
+
if not out or out[0].startswith("-"):
|
|
111
|
+
return None
|
|
112
|
+
while out and _looks_like_env_assignment_token(out[0]):
|
|
113
|
+
out = out[1:]
|
|
114
|
+
return out or None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _strip_verification_runner_prefix(parts: list[str]) -> list[str] | None:
|
|
118
|
+
return strip_verify_runner_prefix(parts)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _normalize_and_unwrap_verification_command(raw: str) -> str | None:
|
|
122
|
+
normalized = _normalize_shell_command_for_match(raw)
|
|
123
|
+
if not normalized:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
while True:
|
|
127
|
+
wrapped = _unwrap_shell_wrapper_command(normalized)
|
|
128
|
+
if not wrapped or wrapped == normalized:
|
|
129
|
+
break
|
|
130
|
+
normalized = wrapped
|
|
131
|
+
return normalized
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _has_disallowed_shell_control_flow(raw: str) -> bool:
|
|
135
|
+
analysis = analyze_verification_command(raw, trusted=True)
|
|
136
|
+
return analysis.shell_control_flow in {"unsafe", "pipeline"} or analysis.rejection_reason in {
|
|
137
|
+
"disallowed_shell_control_flow",
|
|
138
|
+
"unsafe_pipeline",
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _top_level_shell_chain_segments(raw: str) -> tuple[list[str], tuple[str, ...]] | None:
|
|
143
|
+
command = str(raw or "")
|
|
144
|
+
segments: list[str] = []
|
|
145
|
+
separators: list[str] = []
|
|
146
|
+
start = 0
|
|
147
|
+
quote: str | None = None
|
|
148
|
+
escaped = False
|
|
149
|
+
index = 0
|
|
150
|
+
while index < len(command):
|
|
151
|
+
char = command[index]
|
|
152
|
+
if escaped:
|
|
153
|
+
escaped = False
|
|
154
|
+
index += 1
|
|
155
|
+
continue
|
|
156
|
+
if char == "\\" and quote != "'":
|
|
157
|
+
escaped = True
|
|
158
|
+
index += 1
|
|
159
|
+
continue
|
|
160
|
+
if quote is not None:
|
|
161
|
+
if char == quote:
|
|
162
|
+
quote = None
|
|
163
|
+
index += 1
|
|
164
|
+
continue
|
|
165
|
+
if char in {"'", '"'}:
|
|
166
|
+
quote = char
|
|
167
|
+
index += 1
|
|
168
|
+
continue
|
|
169
|
+
if command.startswith("&&", index) or command.startswith("||", index):
|
|
170
|
+
segments.append(command[start:index].strip())
|
|
171
|
+
separators.append(command[index : index + 2])
|
|
172
|
+
index += 2
|
|
173
|
+
start = index
|
|
174
|
+
continue
|
|
175
|
+
if char in {";", "|", "&"}:
|
|
176
|
+
segments.append(command[start:index].strip())
|
|
177
|
+
separators.append(char)
|
|
178
|
+
index += 1
|
|
179
|
+
start = index
|
|
180
|
+
continue
|
|
181
|
+
index += 1
|
|
182
|
+
if quote is not None:
|
|
183
|
+
return None
|
|
184
|
+
if not separators:
|
|
185
|
+
return None
|
|
186
|
+
segments.append(command[start:].strip())
|
|
187
|
+
return segments, tuple(separators)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _expand_simple_verify_command_chain(
|
|
191
|
+
raw: str,
|
|
192
|
+
*,
|
|
193
|
+
workspace_root: Path | None = None,
|
|
194
|
+
) -> list[str]:
|
|
195
|
+
split = _top_level_shell_chain_segments(raw)
|
|
196
|
+
if split is None:
|
|
197
|
+
return [raw]
|
|
198
|
+
segments, separators = split
|
|
199
|
+
if any(separator not in {"&&", ";"} for separator in separators):
|
|
200
|
+
return [raw]
|
|
201
|
+
if any(not segment for segment in segments):
|
|
202
|
+
return [raw]
|
|
203
|
+
|
|
204
|
+
expanded: list[str] = []
|
|
205
|
+
for segment in segments:
|
|
206
|
+
analysis = analyze_verification_command(segment, workspace_root=workspace_root)
|
|
207
|
+
if (
|
|
208
|
+
analysis.rejection_reason
|
|
209
|
+
or analysis.shell_control_flow != "none"
|
|
210
|
+
or analysis.evidentiary_capability != VerificationCommandEvidentiaryCapability.ASSERTIVE
|
|
211
|
+
):
|
|
212
|
+
return [raw]
|
|
213
|
+
expanded.append(segment)
|
|
214
|
+
return expanded
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _canonicalize_verification_command_for_match(raw: str) -> str | None:
|
|
218
|
+
analysis = analyze_verification_command(raw, trusted=True)
|
|
219
|
+
if analysis.rejection_reason:
|
|
220
|
+
return None
|
|
221
|
+
return analysis.canonical_command
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _split_verification_shape_args(args: list[str]) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
225
|
+
options: list[str] = []
|
|
226
|
+
positionals: list[str] = []
|
|
227
|
+
for arg in args:
|
|
228
|
+
if arg == "--":
|
|
229
|
+
continue
|
|
230
|
+
if arg.startswith("-") and arg != "-":
|
|
231
|
+
options.append(arg)
|
|
232
|
+
continue
|
|
233
|
+
positionals.append(arg)
|
|
234
|
+
return tuple(options), tuple(positionals)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _parse_verification_command_shape(raw: str) -> VerificationCommandShape | None:
|
|
238
|
+
canonical = _canonicalize_verification_command_for_match(raw)
|
|
239
|
+
if not canonical:
|
|
240
|
+
return None
|
|
241
|
+
try:
|
|
242
|
+
parts = shlex.split(canonical, posix=True)
|
|
243
|
+
except ValueError:
|
|
244
|
+
return None
|
|
245
|
+
if not parts:
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
head = parts[0]
|
|
249
|
+
tail = parts[1:]
|
|
250
|
+
family: str
|
|
251
|
+
args: list[str]
|
|
252
|
+
|
|
253
|
+
if head in {"pytest", "py.test"}:
|
|
254
|
+
family = "pytest"
|
|
255
|
+
args = tail
|
|
256
|
+
elif head == "unittest":
|
|
257
|
+
family = "unittest"
|
|
258
|
+
args = tail
|
|
259
|
+
elif head == "mypy":
|
|
260
|
+
family = "mypy"
|
|
261
|
+
args = tail
|
|
262
|
+
elif head == "go" and tail and tail[0] == "test":
|
|
263
|
+
family = "go:test"
|
|
264
|
+
args = tail[1:]
|
|
265
|
+
elif head == "cargo" and tail and tail[0] in {"test", "check"}:
|
|
266
|
+
family = f"cargo:{tail[0]}"
|
|
267
|
+
args = tail[1:]
|
|
268
|
+
elif head in {"npm", "pnpm", "yarn"} and tail and tail[0] == "test":
|
|
269
|
+
family = f"{head}:test"
|
|
270
|
+
args = tail[1:]
|
|
271
|
+
elif head in {"make", "just"} and tail and tail[0] in {"test", "check", "verify"}:
|
|
272
|
+
family = f"{head}:{tail[0]}"
|
|
273
|
+
args = tail[1:]
|
|
274
|
+
elif head == "ruff" and tail and tail[0] == "check":
|
|
275
|
+
family = "ruff:check"
|
|
276
|
+
args = tail[1:]
|
|
277
|
+
else:
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
options, positionals = _split_verification_shape_args(args)
|
|
281
|
+
if _NON_VERIFICATION_META_OPTIONS & set(options):
|
|
282
|
+
return None
|
|
283
|
+
if family == "pytest" and _PYTEST_NON_VERIFICATION_OPTIONS & set(options):
|
|
284
|
+
return None
|
|
285
|
+
shape = VerificationCommandShape(
|
|
286
|
+
family=family,
|
|
287
|
+
args=tuple(args),
|
|
288
|
+
options=options,
|
|
289
|
+
positionals=positionals,
|
|
290
|
+
)
|
|
291
|
+
if not _verification_shape_is_real_execution_mode(shape):
|
|
292
|
+
return None
|
|
293
|
+
return shape
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _verification_command_shapes_match(
|
|
297
|
+
*,
|
|
298
|
+
observed: VerificationCommandShape,
|
|
299
|
+
expected: VerificationCommandShape,
|
|
300
|
+
) -> bool:
|
|
301
|
+
if observed.family != expected.family:
|
|
302
|
+
return False
|
|
303
|
+
observed_options = set(observed.options)
|
|
304
|
+
expected_options = set(expected.options)
|
|
305
|
+
if observed.family == "pytest":
|
|
306
|
+
observed_options = {
|
|
307
|
+
option for option in observed_options if not _pytest_option_is_reporter_variant(option)
|
|
308
|
+
}
|
|
309
|
+
expected_options = {
|
|
310
|
+
option for option in expected_options if not _pytest_option_is_reporter_variant(option)
|
|
311
|
+
}
|
|
312
|
+
return expected_options.issubset(observed_options)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _pytest_option_is_reporter_variant(option: str) -> bool:
|
|
316
|
+
return option in _PYTEST_REPORTER_OPTIONS or bool(re.fullmatch(r"-v+", option))
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _effective_verification_command_matches(
|
|
320
|
+
*,
|
|
321
|
+
normalized_cmd: str,
|
|
322
|
+
known_verification_commands: list[str],
|
|
323
|
+
) -> bool:
|
|
324
|
+
observed_canonical = _canonicalize_verification_command_for_match(normalized_cmd)
|
|
325
|
+
if observed_canonical:
|
|
326
|
+
for configured in known_verification_commands:
|
|
327
|
+
expected_canonical = _canonicalize_verification_command_for_match(configured)
|
|
328
|
+
if expected_canonical and observed_canonical == expected_canonical:
|
|
329
|
+
return True
|
|
330
|
+
|
|
331
|
+
observed = _parse_verification_command_shape(normalized_cmd)
|
|
332
|
+
if observed is None:
|
|
333
|
+
return False
|
|
334
|
+
return any(
|
|
335
|
+
_verification_command_shapes_match(observed=observed, expected=expected)
|
|
336
|
+
for expected in (
|
|
337
|
+
_parse_verification_command_shape(configured)
|
|
338
|
+
for configured in known_verification_commands
|
|
339
|
+
)
|
|
340
|
+
if expected is not None
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _verify_run_commands_match_effective_contract(
|
|
345
|
+
*,
|
|
346
|
+
requested_commands: list[str],
|
|
347
|
+
effective_verification_commands: list[str],
|
|
348
|
+
) -> list[str]:
|
|
349
|
+
known = _normalized_verify_commands(effective_verification_commands)
|
|
350
|
+
if not known:
|
|
351
|
+
return []
|
|
352
|
+
incompatible: list[str] = []
|
|
353
|
+
for command in requested_commands:
|
|
354
|
+
normalized_command = _normalize_shell_command_for_match(command)
|
|
355
|
+
if any(
|
|
356
|
+
normalized_command == _normalize_shell_command_for_match(configured)
|
|
357
|
+
for configured in known
|
|
358
|
+
):
|
|
359
|
+
continue
|
|
360
|
+
if not _effective_verification_command_matches(
|
|
361
|
+
normalized_cmd=normalized_command,
|
|
362
|
+
known_verification_commands=known,
|
|
363
|
+
):
|
|
364
|
+
incompatible.append(command)
|
|
365
|
+
return incompatible
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _matching_effective_verification_commands(
|
|
369
|
+
*,
|
|
370
|
+
observed_command: str,
|
|
371
|
+
effective_verification_commands: list[str] | None,
|
|
372
|
+
) -> set[str]:
|
|
373
|
+
known = _normalized_verify_commands(effective_verification_commands or [])
|
|
374
|
+
if not known:
|
|
375
|
+
return set()
|
|
376
|
+
normalized_observed = _normalize_shell_command_for_match(observed_command)
|
|
377
|
+
exact_matches = {
|
|
378
|
+
configured
|
|
379
|
+
for configured in known
|
|
380
|
+
if normalized_observed == _normalize_shell_command_for_match(configured)
|
|
381
|
+
}
|
|
382
|
+
if exact_matches:
|
|
383
|
+
return exact_matches
|
|
384
|
+
observed_canonical = _canonicalize_verification_command_for_match(normalized_observed)
|
|
385
|
+
if observed_canonical:
|
|
386
|
+
exact_matches: set[str] = set()
|
|
387
|
+
for configured in known:
|
|
388
|
+
configured_canonical = _canonicalize_verification_command_for_match(configured)
|
|
389
|
+
if configured_canonical and configured_canonical == observed_canonical:
|
|
390
|
+
exact_matches.add(configured)
|
|
391
|
+
if exact_matches:
|
|
392
|
+
return exact_matches
|
|
393
|
+
|
|
394
|
+
observed = _parse_verification_command_shape(normalized_observed)
|
|
395
|
+
if observed is None:
|
|
396
|
+
return set()
|
|
397
|
+
matches: set[str] = set()
|
|
398
|
+
for configured in known:
|
|
399
|
+
expected = _parse_verification_command_shape(configured)
|
|
400
|
+
if expected is None:
|
|
401
|
+
continue
|
|
402
|
+
if _verification_command_shapes_match(observed=observed, expected=expected):
|
|
403
|
+
matches.add(configured)
|
|
404
|
+
return matches
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _unwrap_shell_wrapper_command(normalized_cmd: str) -> str | None:
|
|
408
|
+
if not normalized_cmd:
|
|
409
|
+
return None
|
|
410
|
+
try:
|
|
411
|
+
parts = shlex.split(normalized_cmd, posix=True)
|
|
412
|
+
except ValueError:
|
|
413
|
+
return None
|
|
414
|
+
if not parts:
|
|
415
|
+
return None
|
|
416
|
+
|
|
417
|
+
head = parts[0]
|
|
418
|
+
if head in {"bash", "sh", "zsh"}:
|
|
419
|
+
if len(parts) == 3 and parts[1] == "-lc":
|
|
420
|
+
return _normalize_shell_command_for_match(parts[2])
|
|
421
|
+
return None
|
|
422
|
+
if head == "fish":
|
|
423
|
+
if len(parts) == 3 and parts[1] == "-c":
|
|
424
|
+
return _normalize_shell_command_for_match(parts[2])
|
|
425
|
+
return None
|
|
426
|
+
if head == "cmd":
|
|
427
|
+
if len(parts) == 3 and parts[1] == "/c":
|
|
428
|
+
return _normalize_shell_command_for_match(parts[2])
|
|
429
|
+
return None
|
|
430
|
+
if head in {"powershell", "pwsh"}:
|
|
431
|
+
if len(parts) == 3 and parts[1] == "-command":
|
|
432
|
+
return _normalize_shell_command_for_match(parts[2])
|
|
433
|
+
return None
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _looks_like_verification_entrypoint(parts: list[str]) -> bool:
|
|
438
|
+
if not parts:
|
|
439
|
+
return False
|
|
440
|
+
head = Path(parts[0]).name
|
|
441
|
+
head_lower = head.lower()
|
|
442
|
+
if head_lower in {
|
|
443
|
+
"pytest",
|
|
444
|
+
"py.test",
|
|
445
|
+
"tox",
|
|
446
|
+
"nox",
|
|
447
|
+
"vitest",
|
|
448
|
+
"jest",
|
|
449
|
+
"mypy",
|
|
450
|
+
"flake8",
|
|
451
|
+
"pylint",
|
|
452
|
+
"ruff",
|
|
453
|
+
"mix",
|
|
454
|
+
"sbt",
|
|
455
|
+
"swift",
|
|
456
|
+
"composer",
|
|
457
|
+
}:
|
|
458
|
+
return True
|
|
459
|
+
if len(parts) >= 2 and head_lower in {
|
|
460
|
+
"make",
|
|
461
|
+
"just",
|
|
462
|
+
"npm",
|
|
463
|
+
"pnpm",
|
|
464
|
+
"yarn",
|
|
465
|
+
"cargo",
|
|
466
|
+
"go",
|
|
467
|
+
"mvn",
|
|
468
|
+
"mvnw",
|
|
469
|
+
"gradle",
|
|
470
|
+
"gradlew",
|
|
471
|
+
"dotnet",
|
|
472
|
+
}:
|
|
473
|
+
return parts[1] in {"test", "check", "verify"}
|
|
474
|
+
if len(parts) >= 2 and head_lower in {"python", "python3"} and parts[1] == "-m":
|
|
475
|
+
return len(parts) >= 3 and parts[2] in {"pytest", "unittest"}
|
|
476
|
+
if len(parts) >= 2 and head_lower == "ruby":
|
|
477
|
+
return any(part.endswith("_test.rb") for part in parts[1:])
|
|
478
|
+
if len(parts) >= 2 and head_lower in {"sh", "bash"}:
|
|
479
|
+
return any("test" in Path(part).name for part in parts[1:])
|
|
480
|
+
return False
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _marker_fallback_is_verification_attempt(normalized_cmd: str) -> bool:
|
|
484
|
+
analysis = analyze_verification_command(normalized_cmd, trusted=True)
|
|
485
|
+
if analysis.rejection_reason:
|
|
486
|
+
return False
|
|
487
|
+
if analysis.command_family is not None:
|
|
488
|
+
return True
|
|
489
|
+
canonical = analysis.canonical_command
|
|
490
|
+
if not canonical:
|
|
491
|
+
return False
|
|
492
|
+
try:
|
|
493
|
+
parts = shlex.split(canonical, posix=True)
|
|
494
|
+
except ValueError:
|
|
495
|
+
return False
|
|
496
|
+
return _looks_like_verification_entrypoint(parts)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _shell_command_is_verification_attempt(
|
|
500
|
+
cmd: str,
|
|
501
|
+
*,
|
|
502
|
+
known_verification_commands: list[str] | None,
|
|
503
|
+
) -> bool:
|
|
504
|
+
from .verification_evidence import classify_verification_evidence
|
|
505
|
+
|
|
506
|
+
evidence = classify_verification_evidence(
|
|
507
|
+
cmd,
|
|
508
|
+
known_verification_commands=known_verification_commands,
|
|
509
|
+
exit_code=0,
|
|
510
|
+
real_execution=True,
|
|
511
|
+
)
|
|
512
|
+
return bool(evidence.allowed_to_satisfy_contract)
|