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,610 @@
|
|
|
1
|
+
"""Reproduction-first protocol for bug-fix-shaped tasks (verification step 5).
|
|
2
|
+
|
|
3
|
+
The failure this targets is a *plausible patch that misses the reported symptom*:
|
|
4
|
+
the agent fixes its interpretation of the bug, verifies that interpretation, and
|
|
5
|
+
finalizes believing it is done. Nothing in steps 1-4 catches it, because every
|
|
6
|
+
signal the agent produced (edits, passing tests, confirmed expectations) is about
|
|
7
|
+
the agent's own reading of the task.
|
|
8
|
+
|
|
9
|
+
The protocol adds one fact the agent cannot manufacture from its interpretation:
|
|
10
|
+
|
|
11
|
+
1. before touching product code, derive a minimal reproduction of the *reported*
|
|
12
|
+
symptom and run it;
|
|
13
|
+
2. it must FAIL on the unpatched tree. A repro that passes pre-fix is evidence the
|
|
14
|
+
interpretation is wrong -- revise the repro (bounded rounds), do not start
|
|
15
|
+
editing;
|
|
16
|
+
3. after patching, the same repro must pass;
|
|
17
|
+
4. the summary states the repro status either way -- never silent success;
|
|
18
|
+
5. repro scaffolding never reaches the delivered diff.
|
|
19
|
+
|
|
20
|
+
Design invariants (identical in spirit to steps 2-4):
|
|
21
|
+
|
|
22
|
+
* Every function here is pure and unit-testable without an LLM call. The single
|
|
23
|
+
exception, ``surviving_repro_artifacts``, takes an explicit root and only stats
|
|
24
|
+
paths (mirroring ``acceptance_contract``'s bounded filesystem probes).
|
|
25
|
+
* Phase classification is a *fact*, never a heuristic: a repro run is pre-fix iff
|
|
26
|
+
no non-artifact repo path had been touched when it ran. Writing the repro is
|
|
27
|
+
itself a material edit, so edit generation cannot discriminate -- the artifact
|
|
28
|
+
set can.
|
|
29
|
+
* Task shape comes from the validated provider-neutral semantic router contract.
|
|
30
|
+
This module maps that enum into protocol state and never interprets user
|
|
31
|
+
language itself.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import re
|
|
37
|
+
from collections.abc import Iterable, Sequence
|
|
38
|
+
from dataclasses import dataclass
|
|
39
|
+
from enum import StrEnum
|
|
40
|
+
from pathlib import Path, PurePosixPath
|
|
41
|
+
from typing import Any
|
|
42
|
+
|
|
43
|
+
from ..branding import env_get
|
|
44
|
+
from .turn_contract import TurnSemantics, TurnTaskShape
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
# Kill-switch (mirrors the evidence-v2 / regression / turn-contract idiom)
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _reproduction_first_enabled(cfg: Any | None) -> bool:
|
|
52
|
+
"""Kill-switch for the reproduction-first protocol (step 5).
|
|
53
|
+
|
|
54
|
+
``ALYSIS_REPRODUCTION_FIRST`` (off/0/false/no/disabled) wins over the config
|
|
55
|
+
value; default is on. When off, repro runs are still captured for telemetry but
|
|
56
|
+
the completion-gate policy and the turn directives revert to legacy.
|
|
57
|
+
"""
|
|
58
|
+
env_value = env_get("ALYSIS_REPRODUCTION_FIRST")
|
|
59
|
+
if env_value is not None:
|
|
60
|
+
normalized = str(env_value).strip().lower()
|
|
61
|
+
if normalized in {"off", "0", "false", "no", "disabled"}:
|
|
62
|
+
return False
|
|
63
|
+
if normalized in {"on", "1", "true", "yes", "enabled"}:
|
|
64
|
+
return True
|
|
65
|
+
return bool(getattr(cfg, "reproduction_first_enabled", True))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# Task shape (derived from the semantic contract)
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TaskShape(StrEnum):
|
|
74
|
+
"""Whether a task reports a symptom that can be reproduced."""
|
|
75
|
+
|
|
76
|
+
#: The task reports defective behavior -- a repro can demonstrate it.
|
|
77
|
+
BUG_FIX = "bug_fix"
|
|
78
|
+
#: Anything else (feature, docs, refactor, question). No repro phase.
|
|
79
|
+
OTHER = "other"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def task_shape_from_turn_semantics(semantics: TurnSemantics | None) -> TaskShape:
|
|
83
|
+
"""Map a validated router verdict to the reproduction protocol's state."""
|
|
84
|
+
|
|
85
|
+
if semantics is None:
|
|
86
|
+
return TaskShape.OTHER
|
|
87
|
+
return TaskShape.BUG_FIX if semantics.task_shape is TurnTaskShape.BUG_FIX else TaskShape.OTHER
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# Repro runs (facts)
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ReproPhase(StrEnum):
|
|
96
|
+
"""When a repro run happened relative to the first product edit."""
|
|
97
|
+
|
|
98
|
+
#: Ran while no non-artifact repo path had been touched -- the unpatched tree.
|
|
99
|
+
PRE_FIX = "pre_fix"
|
|
100
|
+
#: Ran after product code had already been edited.
|
|
101
|
+
POST_FIX = "post_fix"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class ReproStatus(StrEnum):
|
|
105
|
+
"""The reproduction protocol's outcome for a turn."""
|
|
106
|
+
|
|
107
|
+
#: No run of an agent-created artifact was observed at all.
|
|
108
|
+
NOT_ATTEMPTED = "not_attempted"
|
|
109
|
+
#: Pre-fix runs happened but none failed -- the interpretation is unconfirmed.
|
|
110
|
+
NOT_REPRODUCING = "not_reproducing"
|
|
111
|
+
#: A pre-fix run failed; no post-fix run has been observed yet.
|
|
112
|
+
FAILING_PRE_FIX = "failing_pre_fix"
|
|
113
|
+
#: A pre-fix run failed and the latest post-fix run still fails.
|
|
114
|
+
FAILING_POST_FIX = "failing_post_fix"
|
|
115
|
+
#: A post-fix run passes but no failing pre-fix run was ever observed.
|
|
116
|
+
PASSING_UNVALIDATED = "passing_unvalidated"
|
|
117
|
+
#: Failed before the fix, passes after it. The only satisfied state.
|
|
118
|
+
PASSING_POST_FIX = "passing_post_fix"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
#: Bounded revision rounds for a repro that does not reproduce the symptom.
|
|
122
|
+
#: After this many, the run proceeds to the fix and finalizes honestly rather
|
|
123
|
+
#: than deadlocking on a symptom the environment may genuinely not expose.
|
|
124
|
+
MAX_REPRO_REVISION_ROUNDS = 2
|
|
125
|
+
|
|
126
|
+
#: Cap on retained repro runs / artifact paths, so a long turn's state stays small.
|
|
127
|
+
MAX_REPRO_RUNS = 40
|
|
128
|
+
MAX_REPRO_ARTIFACTS = 20
|
|
129
|
+
|
|
130
|
+
#: Trailing punctuation and selector syntax stripped from a command token before
|
|
131
|
+
#: comparing it to an artifact path (``pytest repro_x.py::test_case,``).
|
|
132
|
+
_COMMAND_TOKEN_SPLIT_RE = re.compile(r"[\s;|&()<>]+")
|
|
133
|
+
_TOKEN_TRAILING_JUNK = ",;:'\"`)]}"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _normalize_artifact_path(path: str) -> str:
|
|
137
|
+
cleaned = str(path or "").strip().strip("`'\"").replace("\\", "/")
|
|
138
|
+
while cleaned.startswith("./"):
|
|
139
|
+
cleaned = cleaned[2:]
|
|
140
|
+
return cleaned.strip("/")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _command_path_tokens(command: str) -> list[str]:
|
|
144
|
+
tokens: list[str] = []
|
|
145
|
+
for raw in _COMMAND_TOKEN_SPLIT_RE.split(str(command or "")):
|
|
146
|
+
token = raw.strip().strip("`'\"").rstrip(_TOKEN_TRAILING_JUNK)
|
|
147
|
+
if not token:
|
|
148
|
+
continue
|
|
149
|
+
# pytest node ids (``file.py::test``) and line suffixes (``file.py:12``).
|
|
150
|
+
token = token.split("::", 1)[0]
|
|
151
|
+
normalized = _normalize_artifact_path(token)
|
|
152
|
+
if normalized:
|
|
153
|
+
tokens.append(normalized)
|
|
154
|
+
return tokens
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def match_repro_artifacts(command: str, agent_created_paths: Iterable[str]) -> tuple[str, ...]:
|
|
158
|
+
"""Return the agent-created paths this command executes.
|
|
159
|
+
|
|
160
|
+
Mechanical: a command "runs an artifact" when one of its path-shaped tokens is
|
|
161
|
+
a path the agent created this turn. Matching is exact on the normalized path,
|
|
162
|
+
or on a trailing path segment boundary (``pytest repro_x.py`` executed from the
|
|
163
|
+
directory holding ``tests/repro_x.py``). Bare-basename matching against an
|
|
164
|
+
unrelated file is impossible -- the segment boundary is required.
|
|
165
|
+
"""
|
|
166
|
+
created = [
|
|
167
|
+
normalized
|
|
168
|
+
for normalized in (_normalize_artifact_path(path) for path in agent_created_paths)
|
|
169
|
+
if normalized
|
|
170
|
+
]
|
|
171
|
+
if not created:
|
|
172
|
+
return ()
|
|
173
|
+
tokens = _command_path_tokens(command)
|
|
174
|
+
if not tokens:
|
|
175
|
+
return ()
|
|
176
|
+
matched: list[str] = []
|
|
177
|
+
for artifact in created:
|
|
178
|
+
for token in tokens:
|
|
179
|
+
if (
|
|
180
|
+
token == artifact
|
|
181
|
+
or artifact.endswith(f"/{token}")
|
|
182
|
+
or token.endswith(f"/{artifact}")
|
|
183
|
+
):
|
|
184
|
+
matched.append(artifact)
|
|
185
|
+
break
|
|
186
|
+
return tuple(dict.fromkeys(matched))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass(frozen=True)
|
|
190
|
+
class ReproRun:
|
|
191
|
+
"""One observed execution of an agent-created artifact."""
|
|
192
|
+
|
|
193
|
+
command: str
|
|
194
|
+
artifact_paths: tuple[str, ...]
|
|
195
|
+
phase: ReproPhase
|
|
196
|
+
passed: bool
|
|
197
|
+
exit_code: int | None = None
|
|
198
|
+
#: Non-artifact repo paths already touched when the run happened (bounded).
|
|
199
|
+
product_paths: tuple[str, ...] = ()
|
|
200
|
+
|
|
201
|
+
def as_payload(self) -> dict[str, Any]:
|
|
202
|
+
return {
|
|
203
|
+
"command": self.command,
|
|
204
|
+
"artifact_paths": list(self.artifact_paths),
|
|
205
|
+
"phase": self.phase.value,
|
|
206
|
+
"passed": self.passed,
|
|
207
|
+
"exit_code": self.exit_code,
|
|
208
|
+
"product_paths": list(self.product_paths),
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def classify_repro_phase(
|
|
213
|
+
*,
|
|
214
|
+
touched_repo_paths: Iterable[str],
|
|
215
|
+
created_paths: Iterable[str],
|
|
216
|
+
) -> tuple[ReproPhase, tuple[str, ...]]:
|
|
217
|
+
"""Classify a run as pre-fix or post-fix, and return the product paths.
|
|
218
|
+
|
|
219
|
+
"Product code" is code that existed before this turn: a run is pre-fix exactly
|
|
220
|
+
when no pre-existing repo path has been modified yet. Files the agent authored
|
|
221
|
+
this turn -- the reproduction itself, and any new test it added -- are excluded,
|
|
222
|
+
since creating them changes no existing behavior.
|
|
223
|
+
|
|
224
|
+
This is a fact about recorded edits, never an inference about ordering intent.
|
|
225
|
+
Writing the reproduction is itself a material edit, which is why the edit
|
|
226
|
+
generation cannot discriminate here but the created-path set can.
|
|
227
|
+
"""
|
|
228
|
+
created = {
|
|
229
|
+
normalized
|
|
230
|
+
for normalized in (_normalize_artifact_path(path) for path in created_paths)
|
|
231
|
+
if normalized
|
|
232
|
+
}
|
|
233
|
+
product = sorted(
|
|
234
|
+
normalized
|
|
235
|
+
for normalized in (_normalize_artifact_path(path) for path in touched_repo_paths)
|
|
236
|
+
if normalized and normalized not in created
|
|
237
|
+
)
|
|
238
|
+
phase = ReproPhase.PRE_FIX if not product else ReproPhase.POST_FIX
|
|
239
|
+
return phase, tuple(product[:MAX_REPRO_ARTIFACTS])
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ---------------------------------------------------------------------------
|
|
243
|
+
# Assessment (pure)
|
|
244
|
+
# ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@dataclass(frozen=True)
|
|
248
|
+
class ReproAssessment:
|
|
249
|
+
"""The mechanical state of the reproduction protocol at a decision point."""
|
|
250
|
+
|
|
251
|
+
status: ReproStatus = ReproStatus.NOT_ATTEMPTED
|
|
252
|
+
#: True when the protocol applies at all (bug-fix-shaped execute turn, enabled).
|
|
253
|
+
applicable: bool = False
|
|
254
|
+
artifact_paths: tuple[str, ...] = ()
|
|
255
|
+
failing_pre_fix_command: str = ""
|
|
256
|
+
latest_post_fix_command: str = ""
|
|
257
|
+
revision_rounds: int = 0
|
|
258
|
+
#: Artifacts edited after product code was already changed (guardrail signal).
|
|
259
|
+
edited_after_fix: tuple[str, ...] = ()
|
|
260
|
+
#: Recorded artifacts still present in the tree at finalization.
|
|
261
|
+
surviving_artifacts: tuple[str, ...] = ()
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def satisfied(self) -> bool:
|
|
265
|
+
return self.status == ReproStatus.PASSING_POST_FIX
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def contradicted(self) -> bool:
|
|
269
|
+
"""The repro proves the delivered patch does not fix the reported symptom."""
|
|
270
|
+
return self.status == ReproStatus.FAILING_POST_FIX
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def needs_revision(self) -> bool:
|
|
274
|
+
return (
|
|
275
|
+
self.status == ReproStatus.NOT_REPRODUCING
|
|
276
|
+
and self.revision_rounds < MAX_REPRO_REVISION_ROUNDS
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def as_payload(self) -> dict[str, Any]:
|
|
280
|
+
return {
|
|
281
|
+
"status": self.status.value,
|
|
282
|
+
"applicable": self.applicable,
|
|
283
|
+
"satisfied": self.satisfied,
|
|
284
|
+
"artifact_paths": list(self.artifact_paths),
|
|
285
|
+
"failing_pre_fix_command": self.failing_pre_fix_command,
|
|
286
|
+
"latest_post_fix_command": self.latest_post_fix_command,
|
|
287
|
+
"revision_rounds": self.revision_rounds,
|
|
288
|
+
"edited_after_fix": list(self.edited_after_fix),
|
|
289
|
+
"surviving_artifacts": list(self.surviving_artifacts),
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def assess_reproduction(
|
|
294
|
+
*,
|
|
295
|
+
runs: Sequence[ReproRun],
|
|
296
|
+
applicable: bool,
|
|
297
|
+
artifact_paths: Iterable[str] = (),
|
|
298
|
+
revision_rounds: int = 0,
|
|
299
|
+
edited_after_fix: Iterable[str] = (),
|
|
300
|
+
surviving_artifacts: Iterable[str] = (),
|
|
301
|
+
) -> ReproAssessment:
|
|
302
|
+
"""Derive the protocol status from the observed runs. Pure and deterministic.
|
|
303
|
+
|
|
304
|
+
Precedence, most-specific first:
|
|
305
|
+
|
|
306
|
+
* a failing pre-fix run is the validation anchor -- with one, the latest
|
|
307
|
+
post-fix run decides pass/fail;
|
|
308
|
+
* without one, a passing post-fix run is ``passing_unvalidated`` (the repro
|
|
309
|
+
never demonstrated the bug, so its passing proves nothing);
|
|
310
|
+
* pre-fix runs that all pass are ``not_reproducing`` -- the interpretation is
|
|
311
|
+
wrong and the repro must be revised, not the product code edited.
|
|
312
|
+
"""
|
|
313
|
+
ordered = list(runs)
|
|
314
|
+
artifacts = tuple(
|
|
315
|
+
dict.fromkeys(
|
|
316
|
+
normalized
|
|
317
|
+
for normalized in (_normalize_artifact_path(path) for path in artifact_paths)
|
|
318
|
+
if normalized
|
|
319
|
+
)
|
|
320
|
+
)
|
|
321
|
+
common = {
|
|
322
|
+
"applicable": bool(applicable),
|
|
323
|
+
"artifact_paths": artifacts,
|
|
324
|
+
"revision_rounds": max(0, int(revision_rounds)),
|
|
325
|
+
"edited_after_fix": tuple(
|
|
326
|
+
dict.fromkeys(str(item) for item in edited_after_fix if str(item).strip())
|
|
327
|
+
),
|
|
328
|
+
"surviving_artifacts": tuple(
|
|
329
|
+
dict.fromkeys(str(item) for item in surviving_artifacts if str(item).strip())
|
|
330
|
+
),
|
|
331
|
+
}
|
|
332
|
+
if not ordered:
|
|
333
|
+
return ReproAssessment(status=ReproStatus.NOT_ATTEMPTED, **common)
|
|
334
|
+
|
|
335
|
+
pre_fix = [run for run in ordered if run.phase == ReproPhase.PRE_FIX]
|
|
336
|
+
post_fix = [run for run in ordered if run.phase == ReproPhase.POST_FIX]
|
|
337
|
+
failing_pre_fix = next((run for run in pre_fix if not run.passed), None)
|
|
338
|
+
|
|
339
|
+
if failing_pre_fix is None:
|
|
340
|
+
if pre_fix:
|
|
341
|
+
return ReproAssessment(status=ReproStatus.NOT_REPRODUCING, **common)
|
|
342
|
+
# Only post-fix runs exist: the repro was authored after product code
|
|
343
|
+
# changed, so it never observed the unpatched behavior.
|
|
344
|
+
latest = post_fix[-1]
|
|
345
|
+
status = ReproStatus.PASSING_UNVALIDATED if latest.passed else ReproStatus.FAILING_POST_FIX
|
|
346
|
+
return ReproAssessment(
|
|
347
|
+
status=status,
|
|
348
|
+
latest_post_fix_command=latest.command,
|
|
349
|
+
**common,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
if not post_fix:
|
|
353
|
+
return ReproAssessment(
|
|
354
|
+
status=ReproStatus.FAILING_PRE_FIX,
|
|
355
|
+
failing_pre_fix_command=failing_pre_fix.command,
|
|
356
|
+
**common,
|
|
357
|
+
)
|
|
358
|
+
latest = post_fix[-1]
|
|
359
|
+
return ReproAssessment(
|
|
360
|
+
status=ReproStatus.PASSING_POST_FIX if latest.passed else ReproStatus.FAILING_POST_FIX,
|
|
361
|
+
failing_pre_fix_command=failing_pre_fix.command,
|
|
362
|
+
latest_post_fix_command=latest.command,
|
|
363
|
+
**common,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def repro_blocks_finalization(assessment: ReproAssessment, *, material_edit_count: int) -> bool:
|
|
368
|
+
"""True when the gate must not let this turn finalize yet.
|
|
369
|
+
|
|
370
|
+
A turn that changed nothing is handled by the ``no_material_edits`` stage; the
|
|
371
|
+
repro requirement only bites once product code was actually changed.
|
|
372
|
+
"""
|
|
373
|
+
if not assessment.applicable or material_edit_count <= 0:
|
|
374
|
+
return False
|
|
375
|
+
return not assessment.satisfied
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
# ---------------------------------------------------------------------------
|
|
379
|
+
# Artifact hygiene (the one filesystem-touching helper)
|
|
380
|
+
# ---------------------------------------------------------------------------
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
#: Directory names that mark a path as part of the project's own check surface.
|
|
384
|
+
#: Mirrors ``acceptance_contract._CHECK_PATH_MARKERS``.
|
|
385
|
+
_CHECK_PATH_MARKERS = frozenset(
|
|
386
|
+
{"check", "checks", "test", "tests", "verify", "validation", "validator"}
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def is_delivered_test_path(path: str) -> bool:
|
|
391
|
+
"""True when a path belongs to the project's test/check surface.
|
|
392
|
+
|
|
393
|
+
A reproduction can legitimately *be* a new test the task wanted delivered
|
|
394
|
+
(``tests/test_slugify.py``). Such a file is part of the change, not
|
|
395
|
+
scaffolding, so the cleanup requirement must never demand its deletion. A
|
|
396
|
+
standalone script the agent dropped beside the source is not covered.
|
|
397
|
+
"""
|
|
398
|
+
normalized = _normalize_artifact_path(path)
|
|
399
|
+
if not normalized:
|
|
400
|
+
return False
|
|
401
|
+
pure = PurePosixPath(normalized)
|
|
402
|
+
name = pure.name.casefold()
|
|
403
|
+
parts = {part.casefold() for part in pure.parts[:-1]}
|
|
404
|
+
return (
|
|
405
|
+
name.startswith("test_")
|
|
406
|
+
or name.endswith("_test.py")
|
|
407
|
+
or name.endswith(".test.js")
|
|
408
|
+
or name.endswith(".spec.js")
|
|
409
|
+
or bool(parts & _CHECK_PATH_MARKERS)
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def surviving_repro_artifacts(root: Path, paths: Iterable[str]) -> tuple[str, ...]:
|
|
414
|
+
"""Recorded repro scaffolding that still exists under ``root``.
|
|
415
|
+
|
|
416
|
+
Scaffolding must not reach the delivered diff. Files on the project's test
|
|
417
|
+
surface are excluded — a reproduction written as a real test is part of the
|
|
418
|
+
change, not scaffolding. Paths that resolve outside the workspace, or that no
|
|
419
|
+
longer exist, are not reported. Never raises.
|
|
420
|
+
"""
|
|
421
|
+
surviving: list[str] = []
|
|
422
|
+
for raw in paths:
|
|
423
|
+
normalized = _normalize_artifact_path(raw)
|
|
424
|
+
if not normalized or is_delivered_test_path(normalized):
|
|
425
|
+
continue
|
|
426
|
+
try:
|
|
427
|
+
candidate = (root / normalized).resolve()
|
|
428
|
+
candidate.relative_to(Path(root).resolve())
|
|
429
|
+
except (OSError, ValueError):
|
|
430
|
+
continue
|
|
431
|
+
try:
|
|
432
|
+
if candidate.exists():
|
|
433
|
+
surviving.append(normalized)
|
|
434
|
+
except OSError:
|
|
435
|
+
continue
|
|
436
|
+
return tuple(dict.fromkeys(surviving))
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# ---------------------------------------------------------------------------
|
|
440
|
+
# Directives and advisories (agent-facing text)
|
|
441
|
+
# ---------------------------------------------------------------------------
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
_REPRODUCTION_FIRST_DIRECTIVE_BODY = (
|
|
445
|
+
"- BEFORE editing product code, write a minimal reproduction of the EXACT symptom "
|
|
446
|
+
"the task describes - the same inputs, and an assertion on the expected result "
|
|
447
|
+
"versus what the report says actually happens. Prefer any concrete code block the "
|
|
448
|
+
"report quotes; do not generalize it.\n"
|
|
449
|
+
"- Put it in a new throwaway file (for example `repro_<short-name>.py`) and run it. "
|
|
450
|
+
"It MUST fail on the current, unpatched tree. If the task also asked for a test, "
|
|
451
|
+
"writing it as a real test under the project's test directory counts - that one you "
|
|
452
|
+
"keep.\n"
|
|
453
|
+
"- If it passes before you change anything, your reading of the symptom is wrong: "
|
|
454
|
+
"re-read the report and revise the reproduction. Do not start editing product code "
|
|
455
|
+
"to make a passing reproduction meaningful.\n"
|
|
456
|
+
"- After your fix, run the SAME reproduction unchanged; it must pass. If it does "
|
|
457
|
+
"not, keep iterating on the fix. Never weaken, narrow, or delete the reproduction "
|
|
458
|
+
"to make it pass, and do not edit it after the fix unless its expectation is "
|
|
459
|
+
"demonstrably wrong - if you must, say so explicitly in your final answer.\n"
|
|
460
|
+
"- Delete the reproduction file before you finish. The delivered diff must contain "
|
|
461
|
+
"only the real fix (plus any tests the task actually asked for).\n"
|
|
462
|
+
"- State the reproduction's status in your final answer: that it failed before the "
|
|
463
|
+
"fix and passes after it, or that you could not get it to reproduce."
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
REPRODUCTION_FIRST_TURN_DIRECTIVE = (
|
|
467
|
+
"Reproduction-first protocol (this task reports defective behavior):\n"
|
|
468
|
+
+ _REPRODUCTION_FIRST_DIRECTIVE_BODY
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
#: Router-free variant: no pre-turn task-shape prediction exists, so the model
|
|
472
|
+
#: decides applicability itself. The completion gate binds only after a failing
|
|
473
|
+
#: pre-fix run has actually been observed.
|
|
474
|
+
REPRODUCTION_FIRST_CONDITIONAL_DIRECTIVE = (
|
|
475
|
+
"Reproduction-first protocol — apply it whenever the task reports defective "
|
|
476
|
+
"behavior (an error, a crash, wrong output, a failing command); skip it for "
|
|
477
|
+
"tasks that report no symptom:\n" + _REPRODUCTION_FIRST_DIRECTIVE_BODY
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
REPRO_PRE_EDIT_ADVISORY = (
|
|
481
|
+
"Reproduction advisory: you are changing product code and no reproduction of the "
|
|
482
|
+
"reported symptom has been observed failing on the unpatched tree. Without that, a "
|
|
483
|
+
"passing test after your change only confirms your interpretation of the report, "
|
|
484
|
+
"not the report itself. If you can still do it, revert nothing - just write the "
|
|
485
|
+
"minimal reproduction now, run it, and confirm what it shows. Advisory only - this "
|
|
486
|
+
"does not block your edit."
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
REPRO_NOT_REPRODUCING_ADVISORY = (
|
|
490
|
+
"Your reproduction passed on the unpatched tree, so it does not exercise the "
|
|
491
|
+
"reported symptom. That is a signal your reading of the report is wrong, not a "
|
|
492
|
+
"signal to start editing. Re-read the report - the exact inputs, the exact expected "
|
|
493
|
+
"value, the exact observed value - and revise the reproduction so it fails first. "
|
|
494
|
+
"Do not weaken it to make it fail."
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
REPRO_ARTIFACT_EDITED_AFTER_FIX_ADVISORY = (
|
|
498
|
+
"You edited a reproduction file after changing product code. A reproduction is only "
|
|
499
|
+
"evidence while it stays the one that failed before the fix. Change it only if its "
|
|
500
|
+
"expectation is demonstrably wrong, and say so explicitly in your final answer."
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def build_repro_pre_edit_advisory(*, status: ReproStatus) -> str:
|
|
505
|
+
"""Pre-edit advisory, specialized for a repro that already failed to reproduce."""
|
|
506
|
+
if status == ReproStatus.NOT_REPRODUCING:
|
|
507
|
+
return REPRO_NOT_REPRODUCING_ADVISORY
|
|
508
|
+
return REPRO_PRE_EDIT_ADVISORY
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
# ---------------------------------------------------------------------------
|
|
512
|
+
# Gate nudge / finalization markers (fail honest, never silent)
|
|
513
|
+
# ---------------------------------------------------------------------------
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
_REPRO_NUDGE_LINES = {
|
|
517
|
+
ReproStatus.NOT_ATTEMPTED: (
|
|
518
|
+
"- No reproduction of the reported symptom was run. Write a minimal reproduction "
|
|
519
|
+
"of the exact symptom, run it, and confirm it now passes with your fix in place. "
|
|
520
|
+
"If you cannot make it reproduce at all, say so explicitly instead of implying "
|
|
521
|
+
"the symptom is fixed."
|
|
522
|
+
),
|
|
523
|
+
ReproStatus.NOT_REPRODUCING: (
|
|
524
|
+
"- Your reproduction never failed on the unpatched tree, so nothing has confirmed "
|
|
525
|
+
"you fixed the reported symptom rather than your reading of it. Revise the "
|
|
526
|
+
"reproduction against the exact reported inputs and expected value, or state "
|
|
527
|
+
"plainly that the symptom could not be reproduced."
|
|
528
|
+
),
|
|
529
|
+
ReproStatus.FAILING_PRE_FIX: (
|
|
530
|
+
"- Your reproduction failed before the fix and has not been re-run since. Run it "
|
|
531
|
+
"again, unchanged, and confirm it now passes."
|
|
532
|
+
),
|
|
533
|
+
ReproStatus.FAILING_POST_FIX: (
|
|
534
|
+
"- Your reproduction still fails after the fix: the reported symptom is not "
|
|
535
|
+
"resolved. Keep iterating on the fix and re-run it. Do not weaken or delete the "
|
|
536
|
+
"reproduction to make it pass."
|
|
537
|
+
),
|
|
538
|
+
ReproStatus.PASSING_UNVALIDATED: (
|
|
539
|
+
"- Your reproduction passes, but it was never observed failing on the unpatched "
|
|
540
|
+
"tree, so it does not show your change fixed anything. Confirm it actually "
|
|
541
|
+
"exercises the reported symptom (for example against the pre-fix behavior), or "
|
|
542
|
+
"say plainly that the fix is unvalidated."
|
|
543
|
+
),
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def build_repro_nudge_line(assessment: ReproAssessment) -> str:
|
|
548
|
+
"""The bounded repair nudge line for the current repro status."""
|
|
549
|
+
return _REPRO_NUDGE_LINES.get(assessment.status, "")
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def build_repro_artifacts_nudge_line(paths: Sequence[str] | tuple[str, ...]) -> str:
|
|
553
|
+
"""Repair nudge for scaffolding still present in the working tree."""
|
|
554
|
+
joined = ", ".join(str(item) for item in paths if str(item).strip())
|
|
555
|
+
if not joined:
|
|
556
|
+
return ""
|
|
557
|
+
return (
|
|
558
|
+
f"- Reproduction scaffolding is still in the working tree: {joined}. Delete it "
|
|
559
|
+
"before finalizing so the delivered diff contains only the real change."
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
_REPRO_STATUS_SUMMARY_LINES = {
|
|
564
|
+
ReproStatus.PASSING_POST_FIX: ("Reproduction: failed before the fix, passes after it{detail}."),
|
|
565
|
+
ReproStatus.FAILING_POST_FIX: (
|
|
566
|
+
"⚠️ Reproduction: failed before the fix and STILL FAILS after it{detail} - the "
|
|
567
|
+
"reported symptom is not resolved."
|
|
568
|
+
),
|
|
569
|
+
ReproStatus.FAILING_PRE_FIX: (
|
|
570
|
+
"⚠️ Reproduction: failed before the fix{detail}, but was never re-run afterwards - "
|
|
571
|
+
"the fix is unconfirmed against the reported symptom."
|
|
572
|
+
),
|
|
573
|
+
ReproStatus.NOT_REPRODUCING: (
|
|
574
|
+
"⚠️ Reproduction: could not reproduce the reported symptom on the unpatched tree, "
|
|
575
|
+
"so this change is not validated against the report{detail}."
|
|
576
|
+
),
|
|
577
|
+
ReproStatus.PASSING_UNVALIDATED: (
|
|
578
|
+
"⚠️ Reproduction: passes, but it was never observed failing before the fix{detail}, "
|
|
579
|
+
"so it does not confirm the reported symptom was resolved."
|
|
580
|
+
),
|
|
581
|
+
ReproStatus.NOT_ATTEMPTED: (
|
|
582
|
+
"⚠️ Reproduction: none was run{detail}. This change was not validated against the "
|
|
583
|
+
"reported symptom."
|
|
584
|
+
),
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def build_repro_status_summary(assessment: ReproAssessment) -> str:
|
|
589
|
+
"""The visible reproduction-status line appended to a bug-fix turn's summary.
|
|
590
|
+
|
|
591
|
+
Always emitted for an applicable turn -- a satisfied protocol reports plainly,
|
|
592
|
+
an unsatisfied one reports the deficit. Never silence.
|
|
593
|
+
"""
|
|
594
|
+
if not assessment.applicable:
|
|
595
|
+
return ""
|
|
596
|
+
template = _REPRO_STATUS_SUMMARY_LINES.get(assessment.status, "")
|
|
597
|
+
if not template:
|
|
598
|
+
return ""
|
|
599
|
+
command = (
|
|
600
|
+
assessment.latest_post_fix_command or assessment.failing_pre_fix_command or ""
|
|
601
|
+
).strip()
|
|
602
|
+
detail = f" (`{command}`)" if command else ""
|
|
603
|
+
line = template.format(detail=detail)
|
|
604
|
+
if assessment.edited_after_fix:
|
|
605
|
+
joined = ", ".join(assessment.edited_after_fix)
|
|
606
|
+
line += f" Note: the reproduction was edited after the fix ({joined})."
|
|
607
|
+
if assessment.surviving_artifacts:
|
|
608
|
+
joined = ", ".join(assessment.surviving_artifacts)
|
|
609
|
+
line += f" Reproduction scaffolding left in the tree: {joined}."
|
|
610
|
+
return f"\n\n---\n{line}"
|