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,574 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import shlex
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .verification_command_analysis import (
|
|
11
|
+
VerificationCommandEvidentiaryCapability,
|
|
12
|
+
analyze_verification_command,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class VerificationCommandExecutionMode(StrEnum):
|
|
17
|
+
ARGV = "ARGV"
|
|
18
|
+
TRUSTED_SHELL_EXPRESSION = "TRUSTED_SHELL_EXPRESSION"
|
|
19
|
+
INTERPRETER_SNIPPET = "INTERPRETER_SNIPPET"
|
|
20
|
+
INVALID = "INVALID"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VerificationCommandProvenance(StrEnum):
|
|
24
|
+
HOST_AUTHORITATIVE = "HOST_AUTHORITATIVE"
|
|
25
|
+
EXPLICIT_USER_COMMAND = "EXPLICIT_USER_COMMAND"
|
|
26
|
+
PREEXISTING_REPO_NATIVE = "PREEXISTING_REPO_NATIVE"
|
|
27
|
+
PREEXISTING_TASK_CHECKER = "PREEXISTING_TASK_CHECKER"
|
|
28
|
+
INFERRED_HEURISTIC = "INFERRED_HEURISTIC"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class VerificationCommandTrustLevel(StrEnum):
|
|
32
|
+
TRUSTED = "TRUSTED"
|
|
33
|
+
UNTRUSTED = "UNTRUSTED"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class VerificationCommandRequirement(StrEnum):
|
|
37
|
+
REQUIRED = "REQUIRED"
|
|
38
|
+
ADVISORY = "ADVISORY"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class VerificationCommandValidationStatus(StrEnum):
|
|
42
|
+
VALID = "VALID"
|
|
43
|
+
INVALID = "INVALID"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class VerificationCommandSpec:
|
|
48
|
+
command_id: str
|
|
49
|
+
original_text: str
|
|
50
|
+
display_text: str
|
|
51
|
+
execution_mode: VerificationCommandExecutionMode
|
|
52
|
+
provenance: VerificationCommandProvenance
|
|
53
|
+
trust_level: VerificationCommandTrustLevel
|
|
54
|
+
requirement: VerificationCommandRequirement
|
|
55
|
+
working_directory: str = "."
|
|
56
|
+
timeout_policy: str = "session_verification_timeout"
|
|
57
|
+
acceptance_criterion_ids: tuple[str, ...] = tuple()
|
|
58
|
+
validation_status: VerificationCommandValidationStatus = (
|
|
59
|
+
VerificationCommandValidationStatus.VALID
|
|
60
|
+
)
|
|
61
|
+
rejection_reason: str = ""
|
|
62
|
+
evidentiary_capability: VerificationCommandEvidentiaryCapability = (
|
|
63
|
+
VerificationCommandEvidentiaryCapability.UNKNOWN
|
|
64
|
+
)
|
|
65
|
+
capability_reason: str = ""
|
|
66
|
+
argv: tuple[str, ...] = field(default_factory=tuple)
|
|
67
|
+
|
|
68
|
+
def as_payload(self) -> dict[str, Any]:
|
|
69
|
+
return {
|
|
70
|
+
"command_id": self.command_id,
|
|
71
|
+
"original_text": self.original_text,
|
|
72
|
+
"display_text": self.display_text,
|
|
73
|
+
"execution_mode": self.execution_mode.value,
|
|
74
|
+
"provenance": self.provenance.value,
|
|
75
|
+
"trust_level": self.trust_level.value,
|
|
76
|
+
"requirement": self.requirement.value,
|
|
77
|
+
"working_directory": self.working_directory,
|
|
78
|
+
"timeout_policy": self.timeout_policy,
|
|
79
|
+
"acceptance_criterion_ids": list(self.acceptance_criterion_ids),
|
|
80
|
+
"validation_status": self.validation_status.value,
|
|
81
|
+
"rejection_reason": self.rejection_reason,
|
|
82
|
+
"evidentiary_capability": self.evidentiary_capability.value,
|
|
83
|
+
"capability_reason": self.capability_reason,
|
|
84
|
+
"argv": list(self.argv),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Rejection reasons that mean "Alysis Code could not classify this command",
|
|
89
|
+
# as opposed to "Alysis Code recognized this command and refused it". Only the
|
|
90
|
+
# former is survivable: an unclassifiable command costs the session a
|
|
91
|
+
# verification contract, while a refused one (a vacuous verifier, a masked
|
|
92
|
+
# failure, an unsafe pipeline) would actively manufacture false evidence, so
|
|
93
|
+
# accepting it is worse than stopping.
|
|
94
|
+
UNCLASSIFIABLE_VERIFY_REJECTION_REASONS = frozenset(
|
|
95
|
+
{
|
|
96
|
+
"unknown_verification_capability",
|
|
97
|
+
"unrecognized_command",
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def rejection_reason_is_unclassifiable(reason: str) -> bool:
|
|
103
|
+
return str(reason or "").strip() in UNCLASSIFIABLE_VERIFY_REJECTION_REASONS
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
_TRUSTED_SHELL_PROVENANCE = {
|
|
107
|
+
VerificationCommandProvenance.HOST_AUTHORITATIVE,
|
|
108
|
+
VerificationCommandProvenance.EXPLICIT_USER_COMMAND,
|
|
109
|
+
VerificationCommandProvenance.PREEXISTING_REPO_NATIVE,
|
|
110
|
+
VerificationCommandProvenance.PREEXISTING_TASK_CHECKER,
|
|
111
|
+
}
|
|
112
|
+
_DISALLOWED_VERIFICATION_SHELL_TOKENS = {"||", "&&", ";", "|", "&"}
|
|
113
|
+
_SUPPORTED_VERIFY_HEADS = {
|
|
114
|
+
"pytest",
|
|
115
|
+
"py.test",
|
|
116
|
+
"unittest",
|
|
117
|
+
"mypy",
|
|
118
|
+
"ruff",
|
|
119
|
+
"go",
|
|
120
|
+
"cargo",
|
|
121
|
+
"npm",
|
|
122
|
+
"pnpm",
|
|
123
|
+
"yarn",
|
|
124
|
+
"make",
|
|
125
|
+
"just",
|
|
126
|
+
"test",
|
|
127
|
+
"[",
|
|
128
|
+
"cmp",
|
|
129
|
+
"diff",
|
|
130
|
+
"grep",
|
|
131
|
+
}
|
|
132
|
+
_PYTHON_EXECUTABLES = {"python", "python3", "py"}
|
|
133
|
+
_SHELL_WRAPPER_HEADS = {"bash", "sh", "zsh"}
|
|
134
|
+
_VACUOUS_SUCCESS_HEADS = {"true", ":"}
|
|
135
|
+
_NON_ASSERTIVE_OBSERVATION_HEADS = {
|
|
136
|
+
"cat",
|
|
137
|
+
"echo",
|
|
138
|
+
"find",
|
|
139
|
+
"head",
|
|
140
|
+
"less",
|
|
141
|
+
"ls",
|
|
142
|
+
"more",
|
|
143
|
+
"printf",
|
|
144
|
+
"pwd",
|
|
145
|
+
"tail",
|
|
146
|
+
"type",
|
|
147
|
+
"wc",
|
|
148
|
+
"which",
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def build_verification_command_specs(
|
|
153
|
+
commands: tuple[str, ...] | list[str],
|
|
154
|
+
*,
|
|
155
|
+
source: str,
|
|
156
|
+
contract_type: str = "",
|
|
157
|
+
acceptance_criterion_ids: tuple[str, ...] = tuple(),
|
|
158
|
+
) -> tuple[VerificationCommandSpec, ...]:
|
|
159
|
+
return tuple(
|
|
160
|
+
build_verification_command_spec(
|
|
161
|
+
command,
|
|
162
|
+
source=source,
|
|
163
|
+
contract_type=contract_type,
|
|
164
|
+
acceptance_criterion_ids=acceptance_criterion_ids,
|
|
165
|
+
index=index,
|
|
166
|
+
)
|
|
167
|
+
for index, command in enumerate(commands, start=1)
|
|
168
|
+
if str(command).strip()
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def build_verification_command_spec(
|
|
173
|
+
command: str,
|
|
174
|
+
*,
|
|
175
|
+
source: str,
|
|
176
|
+
contract_type: str = "",
|
|
177
|
+
acceptance_criterion_ids: tuple[str, ...] = tuple(),
|
|
178
|
+
index: int = 1,
|
|
179
|
+
) -> VerificationCommandSpec:
|
|
180
|
+
original = str(command or "").strip()
|
|
181
|
+
provenance = _provenance_for_source(source=source, contract_type=contract_type)
|
|
182
|
+
trust_level = (
|
|
183
|
+
VerificationCommandTrustLevel.TRUSTED
|
|
184
|
+
if provenance in _TRUSTED_SHELL_PROVENANCE
|
|
185
|
+
else VerificationCommandTrustLevel.UNTRUSTED
|
|
186
|
+
)
|
|
187
|
+
requirement = (
|
|
188
|
+
VerificationCommandRequirement.ADVISORY
|
|
189
|
+
if provenance == VerificationCommandProvenance.INFERRED_HEURISTIC
|
|
190
|
+
and contract_type in {"generic_fallback", "unavailable"}
|
|
191
|
+
else VerificationCommandRequirement.REQUIRED
|
|
192
|
+
)
|
|
193
|
+
parsed_parts: tuple[str, ...] = tuple()
|
|
194
|
+
validation_status = VerificationCommandValidationStatus.VALID
|
|
195
|
+
rejection_reason = ""
|
|
196
|
+
execution_mode = VerificationCommandExecutionMode.ARGV
|
|
197
|
+
analysis = analyze_verification_command(
|
|
198
|
+
original,
|
|
199
|
+
source=source,
|
|
200
|
+
contract_type=contract_type,
|
|
201
|
+
trusted=_source_is_trusted(source=source, contract_type=contract_type),
|
|
202
|
+
)
|
|
203
|
+
evidentiary_capability = analysis.evidentiary_capability
|
|
204
|
+
capability_reason = analysis.capability_reason
|
|
205
|
+
if analysis.parts:
|
|
206
|
+
parsed_parts = analysis.parts
|
|
207
|
+
if not original:
|
|
208
|
+
validation_status = VerificationCommandValidationStatus.INVALID
|
|
209
|
+
execution_mode = VerificationCommandExecutionMode.INVALID
|
|
210
|
+
rejection_reason = "empty_command"
|
|
211
|
+
else:
|
|
212
|
+
try:
|
|
213
|
+
tuple(shlex.split(original, posix=True))
|
|
214
|
+
except ValueError as exc:
|
|
215
|
+
validation_status = VerificationCommandValidationStatus.INVALID
|
|
216
|
+
execution_mode = VerificationCommandExecutionMode.INVALID
|
|
217
|
+
rejection_reason = f"parse_error: {exc}"
|
|
218
|
+
else:
|
|
219
|
+
requires_shell_expression = (
|
|
220
|
+
analysis.shell_control_flow != "none"
|
|
221
|
+
or analysis.unwrapped_command != analysis.normalized_command
|
|
222
|
+
)
|
|
223
|
+
if not analysis.parts and not analysis.rejection_reason:
|
|
224
|
+
validation_status = VerificationCommandValidationStatus.INVALID
|
|
225
|
+
execution_mode = VerificationCommandExecutionMode.INVALID
|
|
226
|
+
rejection_reason = analysis.inconclusive_reason or "unrecognized_command"
|
|
227
|
+
elif analysis.rejection_reason:
|
|
228
|
+
validation_status = VerificationCommandValidationStatus.INVALID
|
|
229
|
+
execution_mode = VerificationCommandExecutionMode.INVALID
|
|
230
|
+
rejection_reason = analysis.rejection_reason
|
|
231
|
+
elif (
|
|
232
|
+
analysis.evidentiary_capability
|
|
233
|
+
!= VerificationCommandEvidentiaryCapability.ASSERTIVE
|
|
234
|
+
):
|
|
235
|
+
validation_status = VerificationCommandValidationStatus.INVALID
|
|
236
|
+
execution_mode = VerificationCommandExecutionMode.INVALID
|
|
237
|
+
rejection_reason = analysis.inconclusive_reason or "unknown_verification_capability"
|
|
238
|
+
elif requires_shell_expression:
|
|
239
|
+
if provenance in _TRUSTED_SHELL_PROVENANCE:
|
|
240
|
+
execution_mode = VerificationCommandExecutionMode.TRUSTED_SHELL_EXPRESSION
|
|
241
|
+
else:
|
|
242
|
+
validation_status = VerificationCommandValidationStatus.INVALID
|
|
243
|
+
execution_mode = VerificationCommandExecutionMode.INVALID
|
|
244
|
+
rejection_reason = "untrusted_shell_expression"
|
|
245
|
+
elif _is_python_interpreter_snippet(parsed_parts):
|
|
246
|
+
execution_mode = VerificationCommandExecutionMode.INTERPRETER_SNIPPET
|
|
247
|
+
command_id = _stable_command_id(
|
|
248
|
+
source=source,
|
|
249
|
+
contract_type=contract_type,
|
|
250
|
+
command=original,
|
|
251
|
+
index=index,
|
|
252
|
+
)
|
|
253
|
+
return VerificationCommandSpec(
|
|
254
|
+
command_id=command_id,
|
|
255
|
+
original_text=original,
|
|
256
|
+
display_text=original,
|
|
257
|
+
execution_mode=execution_mode,
|
|
258
|
+
provenance=provenance,
|
|
259
|
+
trust_level=trust_level,
|
|
260
|
+
requirement=requirement,
|
|
261
|
+
acceptance_criterion_ids=acceptance_criterion_ids,
|
|
262
|
+
validation_status=validation_status,
|
|
263
|
+
rejection_reason=rejection_reason,
|
|
264
|
+
evidentiary_capability=evidentiary_capability,
|
|
265
|
+
capability_reason=capability_reason,
|
|
266
|
+
argv=parsed_parts if execution_mode == VerificationCommandExecutionMode.ARGV else tuple(),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def trusted_shell_expression_commands(
|
|
271
|
+
commands: tuple[str, ...] | list[str],
|
|
272
|
+
*,
|
|
273
|
+
source: str,
|
|
274
|
+
contract_type: str = "",
|
|
275
|
+
) -> set[str]:
|
|
276
|
+
return {
|
|
277
|
+
_normalize_exact_command(spec.original_text)
|
|
278
|
+
for spec in build_verification_command_specs(
|
|
279
|
+
commands,
|
|
280
|
+
source=source,
|
|
281
|
+
contract_type=contract_type,
|
|
282
|
+
)
|
|
283
|
+
if spec.execution_mode == VerificationCommandExecutionMode.TRUSTED_SHELL_EXPRESSION
|
|
284
|
+
and spec.validation_status == VerificationCommandValidationStatus.VALID
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def command_matches_trusted_shell_expression(
|
|
289
|
+
observed_command: str,
|
|
290
|
+
known_commands: tuple[str, ...] | list[str],
|
|
291
|
+
*,
|
|
292
|
+
source: str,
|
|
293
|
+
contract_type: str = "",
|
|
294
|
+
) -> set[str]:
|
|
295
|
+
observed = _normalize_exact_command(observed_command)
|
|
296
|
+
if not observed:
|
|
297
|
+
return set()
|
|
298
|
+
trusted = trusted_shell_expression_commands(
|
|
299
|
+
known_commands,
|
|
300
|
+
source=source,
|
|
301
|
+
contract_type=contract_type,
|
|
302
|
+
)
|
|
303
|
+
return {command for command in trusted if observed == command}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def command_specs_payload(specs: tuple[VerificationCommandSpec, ...]) -> list[dict[str, Any]]:
|
|
307
|
+
return [spec.as_payload() for spec in specs]
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _source_is_trusted(*, source: str, contract_type: str) -> bool:
|
|
311
|
+
provenance = _provenance_for_source(source=source, contract_type=contract_type)
|
|
312
|
+
return provenance in _TRUSTED_SHELL_PROVENANCE
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _provenance_for_source(
|
|
316
|
+
*,
|
|
317
|
+
source: str,
|
|
318
|
+
contract_type: str,
|
|
319
|
+
) -> VerificationCommandProvenance:
|
|
320
|
+
source = str(source or "")
|
|
321
|
+
contract_type = str(contract_type or "")
|
|
322
|
+
if source == "environment.authoritative_verification_commands" or contract_type in {
|
|
323
|
+
"authoritative_override",
|
|
324
|
+
"explicit_override",
|
|
325
|
+
}:
|
|
326
|
+
return (
|
|
327
|
+
VerificationCommandProvenance.HOST_AUTHORITATIVE
|
|
328
|
+
if source == "environment.authoritative_verification_commands"
|
|
329
|
+
else VerificationCommandProvenance.EXPLICIT_USER_COMMAND
|
|
330
|
+
)
|
|
331
|
+
if source == "cli.verify_cmd" or source.startswith("task_refinement.explicit"):
|
|
332
|
+
return VerificationCommandProvenance.EXPLICIT_USER_COMMAND
|
|
333
|
+
if source == "task_refinement.explicit_user_command" or contract_type == "task_acceptance":
|
|
334
|
+
return VerificationCommandProvenance.EXPLICIT_USER_COMMAND
|
|
335
|
+
if source == "repo_scan.likely_test_commands" or contract_type == "repo_native":
|
|
336
|
+
return VerificationCommandProvenance.PREEXISTING_REPO_NATIVE
|
|
337
|
+
if source.startswith("task_refinement.") and contract_type == "task_inferred":
|
|
338
|
+
return VerificationCommandProvenance.INFERRED_HEURISTIC
|
|
339
|
+
return VerificationCommandProvenance.INFERRED_HEURISTIC
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _normalize_exact_command(command: str) -> str:
|
|
343
|
+
return " ".join(str(command or "").strip().split())
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _has_disallowed_shell_control_flow(raw: str) -> bool:
|
|
347
|
+
analysis = analyze_verification_command(raw, trusted=True)
|
|
348
|
+
return analysis.shell_control_flow in {"unsafe", "pipeline"} or analysis.rejection_reason in {
|
|
349
|
+
"disallowed_shell_control_flow",
|
|
350
|
+
"unsafe_pipeline",
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _normalize_and_unwrap_shell_command(raw: str) -> str:
|
|
355
|
+
normalized = _normalize_exact_command(raw)
|
|
356
|
+
while True:
|
|
357
|
+
wrapped = _unwrap_shell_wrapper_command(normalized)
|
|
358
|
+
if not wrapped or wrapped == normalized:
|
|
359
|
+
return normalized
|
|
360
|
+
normalized = wrapped
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _unwrap_shell_wrapper_command(normalized: str) -> str | None:
|
|
364
|
+
try:
|
|
365
|
+
parts = shlex.split(normalized, posix=True)
|
|
366
|
+
except ValueError:
|
|
367
|
+
return None
|
|
368
|
+
if not parts:
|
|
369
|
+
return None
|
|
370
|
+
head = Path(parts[0]).name.casefold()
|
|
371
|
+
if head in _SHELL_WRAPPER_HEADS and len(parts) == 3 and parts[1] == "-lc":
|
|
372
|
+
return _normalize_exact_command(parts[2])
|
|
373
|
+
if head == "fish" and len(parts) == 3 and parts[1] == "-c":
|
|
374
|
+
return _normalize_exact_command(parts[2])
|
|
375
|
+
if head == "cmd" and len(parts) == 3 and parts[1].casefold() == "/c":
|
|
376
|
+
return _normalize_exact_command(parts[2])
|
|
377
|
+
if head in {"powershell", "pwsh"} and len(parts) == 3 and parts[1].casefold() == "-command":
|
|
378
|
+
return _normalize_exact_command(parts[2])
|
|
379
|
+
return None
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _shell_tokens(raw: str) -> list[str] | None:
|
|
383
|
+
normalized = _normalize_and_unwrap_shell_command(raw)
|
|
384
|
+
if not normalized:
|
|
385
|
+
return None
|
|
386
|
+
try:
|
|
387
|
+
lexer = shlex.shlex(normalized, posix=True, punctuation_chars="|&;")
|
|
388
|
+
lexer.whitespace_split = True
|
|
389
|
+
lexer.commenters = ""
|
|
390
|
+
return list(lexer)
|
|
391
|
+
except ValueError:
|
|
392
|
+
return None
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _semantic_verification_rejection_reason(
|
|
396
|
+
raw: str,
|
|
397
|
+
parsed_parts: tuple[str, ...],
|
|
398
|
+
) -> str:
|
|
399
|
+
tokens = _shell_tokens(raw)
|
|
400
|
+
if tokens and _has_failure_masking_control_flow(tokens):
|
|
401
|
+
return "failure_masking_control_flow"
|
|
402
|
+
|
|
403
|
+
unwrapped = _normalize_and_unwrap_shell_command(raw)
|
|
404
|
+
try:
|
|
405
|
+
parts = tuple(shlex.split(unwrapped, posix=True))
|
|
406
|
+
except ValueError:
|
|
407
|
+
parts = parsed_parts
|
|
408
|
+
if _is_vacuous_success_command(parts):
|
|
409
|
+
return "vacuous_verifier"
|
|
410
|
+
if _is_non_assertive_observation_command(parts):
|
|
411
|
+
return "non_assertive_observation"
|
|
412
|
+
if _is_non_assertive_python_snippet(parts):
|
|
413
|
+
return "vacuous_verifier"
|
|
414
|
+
if _is_compile_only_python_command(parts):
|
|
415
|
+
return "non_assertive_observation"
|
|
416
|
+
return ""
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _has_failure_masking_control_flow(tokens: list[str]) -> bool:
|
|
420
|
+
for index, token in enumerate(tokens[:-1]):
|
|
421
|
+
if token not in {"||", ";", "|"}:
|
|
422
|
+
continue
|
|
423
|
+
right = _next_simple_command_tokens(tokens[index + 1 :])
|
|
424
|
+
if _is_vacuous_success_command(tuple(right)):
|
|
425
|
+
return True
|
|
426
|
+
if token != "|" and _is_non_assertive_observation_command(tuple(right)):
|
|
427
|
+
return True
|
|
428
|
+
return False
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _next_simple_command_tokens(tokens: list[str]) -> list[str]:
|
|
432
|
+
out: list[str] = []
|
|
433
|
+
for token in tokens:
|
|
434
|
+
if token in _DISALLOWED_VERIFICATION_SHELL_TOKENS:
|
|
435
|
+
break
|
|
436
|
+
out.append(token)
|
|
437
|
+
return out
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _command_head(parts: tuple[str, ...]) -> str:
|
|
441
|
+
if not parts:
|
|
442
|
+
return ""
|
|
443
|
+
head = Path(parts[0].replace("\\", "/")).name.casefold()
|
|
444
|
+
if head.endswith(".exe"):
|
|
445
|
+
head = head[:-4]
|
|
446
|
+
return head
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def _is_vacuous_success_command(parts: tuple[str, ...]) -> bool:
|
|
450
|
+
if not parts:
|
|
451
|
+
return False
|
|
452
|
+
head = _command_head(parts)
|
|
453
|
+
if len(parts) == 1 and (head in _VACUOUS_SUCCESS_HEADS or parts[0].strip() == ":"):
|
|
454
|
+
return True
|
|
455
|
+
if len(parts) == 2 and head in {"exit", "return"} and parts[1] == "0":
|
|
456
|
+
return True
|
|
457
|
+
return _is_non_assertive_python_snippet(parts)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _is_non_assertive_observation_command(parts: tuple[str, ...]) -> bool:
|
|
461
|
+
if not parts:
|
|
462
|
+
return False
|
|
463
|
+
head = _command_head(parts)
|
|
464
|
+
if head in {"grep"}:
|
|
465
|
+
return False
|
|
466
|
+
return head in _NON_ASSERTIVE_OBSERVATION_HEADS
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _is_non_assertive_python_snippet(parts: tuple[str, ...]) -> bool:
|
|
470
|
+
if not _is_python_interpreter_snippet(parts):
|
|
471
|
+
return False
|
|
472
|
+
snippet = " ".join(parts[2:]).strip().casefold()
|
|
473
|
+
compact = "".join(snippet.split())
|
|
474
|
+
if compact in {
|
|
475
|
+
"pass",
|
|
476
|
+
"print('ok')",
|
|
477
|
+
'print("ok")',
|
|
478
|
+
"print(1)",
|
|
479
|
+
"sys.exit(0)",
|
|
480
|
+
"raisesystemexit(0)",
|
|
481
|
+
}:
|
|
482
|
+
return True
|
|
483
|
+
if compact.startswith("importsys;") and compact.endswith("sys.exit(0)"):
|
|
484
|
+
return True
|
|
485
|
+
return False
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _is_compile_only_python_command(parts: tuple[str, ...]) -> bool:
|
|
489
|
+
if len(parts) < 3:
|
|
490
|
+
return False
|
|
491
|
+
head = _command_head(parts)
|
|
492
|
+
return (
|
|
493
|
+
head in _PYTHON_EXECUTABLES
|
|
494
|
+
and parts[1] == "-m"
|
|
495
|
+
and parts[2]
|
|
496
|
+
in {
|
|
497
|
+
"compileall",
|
|
498
|
+
"py_compile",
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _parse_verification_command_shape(raw: str) -> tuple[str, ...] | None:
|
|
504
|
+
try:
|
|
505
|
+
parts = shlex.split(raw, posix=True)
|
|
506
|
+
except ValueError:
|
|
507
|
+
return None
|
|
508
|
+
if not parts:
|
|
509
|
+
return None
|
|
510
|
+
while parts and _looks_like_env_assignment(parts[0]):
|
|
511
|
+
parts = parts[1:]
|
|
512
|
+
if not parts:
|
|
513
|
+
return None
|
|
514
|
+
if parts[0] == "env":
|
|
515
|
+
parts = parts[1:]
|
|
516
|
+
while parts and _looks_like_env_assignment(parts[0]):
|
|
517
|
+
parts = parts[1:]
|
|
518
|
+
if (
|
|
519
|
+
len(parts) >= 3
|
|
520
|
+
and Path(parts[0]).name.casefold() in _PYTHON_EXECUTABLES
|
|
521
|
+
and parts[1] == "-m"
|
|
522
|
+
):
|
|
523
|
+
parts = [parts[2], *parts[3:]]
|
|
524
|
+
if not parts:
|
|
525
|
+
return None
|
|
526
|
+
head = Path(parts[0]).name.casefold()
|
|
527
|
+
if head not in _SUPPORTED_VERIFY_HEADS:
|
|
528
|
+
return None
|
|
529
|
+
if head in {"test", "["}:
|
|
530
|
+
return tuple(parts) if len(parts) >= 3 else None
|
|
531
|
+
if head in {"cmp", "diff"}:
|
|
532
|
+
operands = [part for part in parts[1:] if part != "--" and not part.startswith("-")]
|
|
533
|
+
return tuple(parts) if len(operands) >= 2 else None
|
|
534
|
+
if head == "grep":
|
|
535
|
+
has_quiet = any(part in {"-q", "--quiet"} for part in parts[1:])
|
|
536
|
+
operands = [part for part in parts[1:] if part != "--" and not part.startswith("-")]
|
|
537
|
+
return tuple(parts) if has_quiet and len(operands) >= 2 else None
|
|
538
|
+
if head == "ruff" and (len(parts) < 2 or parts[1] != "check"):
|
|
539
|
+
return None
|
|
540
|
+
if head in {"go", "cargo", "npm", "pnpm", "yarn"} and len(parts) < 2:
|
|
541
|
+
return None
|
|
542
|
+
if head == "go" and parts[1] != "test":
|
|
543
|
+
return None
|
|
544
|
+
if head == "cargo" and parts[1] not in {"test", "check"}:
|
|
545
|
+
return None
|
|
546
|
+
if head in {"npm", "pnpm", "yarn"} and parts[1] != "test":
|
|
547
|
+
return None
|
|
548
|
+
return tuple(parts)
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _looks_like_env_assignment(part: str) -> bool:
|
|
552
|
+
if "=" not in part or not part:
|
|
553
|
+
return False
|
|
554
|
+
name = part.split("=", 1)[0]
|
|
555
|
+
return name.replace("_", "a").isalnum() and not name[0].isdigit()
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _is_python_interpreter_snippet(parts: tuple[str, ...]) -> bool:
|
|
559
|
+
return (
|
|
560
|
+
len(parts) >= 3
|
|
561
|
+
and Path(parts[0]).name.casefold() in _PYTHON_EXECUTABLES
|
|
562
|
+
and parts[1] == "-c"
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _stable_command_id(
|
|
567
|
+
*,
|
|
568
|
+
source: str,
|
|
569
|
+
contract_type: str,
|
|
570
|
+
command: str,
|
|
571
|
+
index: int,
|
|
572
|
+
) -> str:
|
|
573
|
+
digest = hashlib.sha256(f"{source}\0{contract_type}\0{index}\0{command}".encode()).hexdigest()
|
|
574
|
+
return f"vc_{digest[:16]}"
|