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,942 @@
|
|
|
1
|
+
# ruff: noqa: F401,F403,F405,I001
|
|
2
|
+
# Legacy split module: dependencies are synced by cli_surface.py.
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from contextvars import ContextVar
|
|
6
|
+
|
|
7
|
+
from .cli_common import *
|
|
8
|
+
|
|
9
|
+
_PICKER_NARROW_OVERRIDE = ContextVar("_PICKER_NARROW_OVERRIDE", default=None)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _picker_row_styles(*, selected: bool) -> tuple[str, str]:
|
|
13
|
+
if selected:
|
|
14
|
+
return STYLE_SELECTED_LABEL, STYLE_SELECTED_DESC
|
|
15
|
+
return STYLE_DESELECTED_LABEL, STYLE_DESELECTED_DESC
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _picker_hint_text() -> str:
|
|
19
|
+
return "Up/Down navigate / Enter confirm / Esc cancel"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _picker_label_text(label: str, *, selected: bool) -> str:
|
|
23
|
+
text = str(label or "")
|
|
24
|
+
if not selected:
|
|
25
|
+
return text
|
|
26
|
+
match = re.match(r"^(\s*\d+\))\s*(.+)$", text)
|
|
27
|
+
if match:
|
|
28
|
+
return f"{match.group(1)} ({match.group(2)})"
|
|
29
|
+
return f"({text})"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _fallback_picker_is_narrow_terminal() -> bool:
|
|
33
|
+
return _terminal_width() < 88
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _picker_is_narrow_terminal() -> bool:
|
|
37
|
+
default = globals().get("_is_narrow_terminal") or _fallback_picker_is_narrow_terminal
|
|
38
|
+
return bool(_patchable("_is_narrow_terminal", default)())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _forge_picker_row_renderables(*, label: str, desc: str, selected: bool) -> list[Any]:
|
|
42
|
+
from rich.text import Text
|
|
43
|
+
|
|
44
|
+
label_style, desc_style = _picker_row_styles(selected=selected)
|
|
45
|
+
renderables: list[Any] = [
|
|
46
|
+
Text.assemble(
|
|
47
|
+
("│ ", "bright_black"),
|
|
48
|
+
(_picker_label_text(label, selected=selected), label_style),
|
|
49
|
+
)
|
|
50
|
+
]
|
|
51
|
+
if str(desc or "").strip():
|
|
52
|
+
renderables.append(
|
|
53
|
+
Text.assemble(
|
|
54
|
+
("│ ", "bright_black"),
|
|
55
|
+
(str(desc), desc_style),
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
return renderables
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _plan_mode_picker_row_renderables(*, label: str, desc: str, selected: bool) -> list[Any]:
|
|
62
|
+
from rich.text import Text
|
|
63
|
+
|
|
64
|
+
label_style, desc_style = _picker_row_styles(selected=selected)
|
|
65
|
+
renderables: list[Any] = [
|
|
66
|
+
Text.assemble(
|
|
67
|
+
(" ", "bright_black"),
|
|
68
|
+
(_picker_label_text(label, selected=selected), label_style),
|
|
69
|
+
)
|
|
70
|
+
]
|
|
71
|
+
if str(desc or "").strip():
|
|
72
|
+
renderables.append(
|
|
73
|
+
Text.assemble(
|
|
74
|
+
(" ", "bright_black"),
|
|
75
|
+
(str(desc), desc_style),
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
return renderables
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _plan_mode_picker_hint_renderable() -> Any:
|
|
82
|
+
from rich.text import Text
|
|
83
|
+
|
|
84
|
+
return Text.assemble(
|
|
85
|
+
(" ", "bright_black"),
|
|
86
|
+
(_picker_hint_text(), "dim"),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _selectable_options_panel(
|
|
91
|
+
*,
|
|
92
|
+
title: str,
|
|
93
|
+
rows: list[tuple[str, str, str]],
|
|
94
|
+
selected_value: str | None = None,
|
|
95
|
+
interactive: bool = False,
|
|
96
|
+
) -> Any:
|
|
97
|
+
from rich.console import Group
|
|
98
|
+
from rich.text import Text
|
|
99
|
+
|
|
100
|
+
selected = (selected_value or "").strip().casefold()
|
|
101
|
+
renderables: list[Any] = (
|
|
102
|
+
[Text(str(title or ""), style=STYLE_EMPHASIS)] if str(title or "").strip() else []
|
|
103
|
+
)
|
|
104
|
+
narrow_override = _PICKER_NARROW_OVERRIDE.get()
|
|
105
|
+
is_narrow = (
|
|
106
|
+
bool(narrow_override) if narrow_override is not None else _picker_is_narrow_terminal()
|
|
107
|
+
)
|
|
108
|
+
if is_narrow:
|
|
109
|
+
for value, label, desc in rows:
|
|
110
|
+
row_selected = str(value).strip().casefold() == selected
|
|
111
|
+
label_style, desc_style = _picker_row_styles(selected=row_selected)
|
|
112
|
+
renderables.append(
|
|
113
|
+
Text.assemble(
|
|
114
|
+
(_picker_label_text(label, selected=row_selected), label_style),
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
if str(desc or "").strip():
|
|
118
|
+
renderables.append(
|
|
119
|
+
Text.assemble(
|
|
120
|
+
(" ", "bright_black"),
|
|
121
|
+
(str(desc), desc_style),
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
if interactive:
|
|
125
|
+
renderables.append(Text(_picker_hint_text(), style="dim"))
|
|
126
|
+
return Group(*renderables)
|
|
127
|
+
|
|
128
|
+
table = _Table(show_header=False, box=None, expand=True, padding=(0, 1), collapse_padding=True)
|
|
129
|
+
table.add_column("option", no_wrap=True, ratio=2)
|
|
130
|
+
table.add_column("description", no_wrap=False, ratio=5, overflow="fold")
|
|
131
|
+
for value, label, desc in rows:
|
|
132
|
+
row_selected = str(value).strip().casefold() == selected
|
|
133
|
+
label_style, desc_style = _picker_row_styles(selected=row_selected)
|
|
134
|
+
table.add_row(
|
|
135
|
+
Text(_picker_label_text(str(label), selected=row_selected), style=label_style),
|
|
136
|
+
Text(str(desc), style=desc_style),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
renderables.append(table)
|
|
140
|
+
if interactive:
|
|
141
|
+
renderables.append(Text(_picker_hint_text(), style="dim"))
|
|
142
|
+
return Group(*renderables)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _terminal_dimensions() -> tuple[int, int]:
|
|
146
|
+
size = shutil.get_terminal_size(fallback=(80, 24))
|
|
147
|
+
columns = int(getattr(size, "columns", 80) or 80)
|
|
148
|
+
rows = int(getattr(size, "lines", 24) or 24)
|
|
149
|
+
if columns < 1:
|
|
150
|
+
columns = 1
|
|
151
|
+
if rows < 1:
|
|
152
|
+
rows = 1
|
|
153
|
+
return columns, rows
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _terminal_too_small() -> bool:
|
|
157
|
+
columns, rows = _patchable("_terminal_dimensions", _terminal_dimensions)()
|
|
158
|
+
return columns < _MIN_TERMINAL_COLUMNS or rows < _MIN_TERMINAL_ROWS
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _terminal_too_small_panel() -> Panel:
|
|
162
|
+
from rich.align import Align
|
|
163
|
+
from rich.text import Text
|
|
164
|
+
|
|
165
|
+
columns, rows = _patchable("_terminal_dimensions", _terminal_dimensions)()
|
|
166
|
+
message = Text("Terminal too small - please resize", style="bold yellow")
|
|
167
|
+
details = Text(
|
|
168
|
+
f"Current: {columns}x{rows} Minimum: {_MIN_TERMINAL_COLUMNS}x{_MIN_TERMINAL_ROWS}",
|
|
169
|
+
style="dim",
|
|
170
|
+
)
|
|
171
|
+
content = _table_grid(expand=True)
|
|
172
|
+
content.add_row("")
|
|
173
|
+
content.add_row(Align.center(message))
|
|
174
|
+
content.add_row("")
|
|
175
|
+
content.add_row(Align.center(details))
|
|
176
|
+
content.add_row("")
|
|
177
|
+
return _Panel(content, title="Resize Required", border_style="yellow")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _clear_terminal_screen(*, console: Console) -> None:
|
|
181
|
+
stream = console.file if getattr(console, "file", None) is not None else sys.stdout
|
|
182
|
+
try:
|
|
183
|
+
stream.write("\x1b[2J\x1b[H")
|
|
184
|
+
stream.flush()
|
|
185
|
+
except Exception: # noqa: BLE001
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@contextmanager
|
|
190
|
+
def _watch_terminal_resize() -> Any:
|
|
191
|
+
if not hasattr(signal, "SIGWINCH"):
|
|
192
|
+
yield lambda: False
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
resized = threading.Event()
|
|
196
|
+
try:
|
|
197
|
+
previous_handler = signal.getsignal(signal.SIGWINCH)
|
|
198
|
+
except Exception: # noqa: BLE001
|
|
199
|
+
yield lambda: False
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
def _on_resize(_signum: int, _frame: Any) -> None:
|
|
203
|
+
resized.set()
|
|
204
|
+
|
|
205
|
+
installed = False
|
|
206
|
+
try:
|
|
207
|
+
signal.signal(signal.SIGWINCH, _on_resize)
|
|
208
|
+
installed = True
|
|
209
|
+
except Exception: # noqa: BLE001
|
|
210
|
+
installed = False
|
|
211
|
+
|
|
212
|
+
def _consume_resize() -> bool:
|
|
213
|
+
if resized.is_set():
|
|
214
|
+
resized.clear()
|
|
215
|
+
return True
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
yield _consume_resize
|
|
220
|
+
finally:
|
|
221
|
+
if installed:
|
|
222
|
+
try:
|
|
223
|
+
signal.signal(signal.SIGWINCH, previous_handler)
|
|
224
|
+
except Exception: # noqa: BLE001
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _read_input_keys_with_timeout(*, input_reader: Any, timeout_s: float) -> list[Any]:
|
|
229
|
+
fileno_fn = getattr(input_reader, "fileno", None)
|
|
230
|
+
if callable(fileno_fn):
|
|
231
|
+
try:
|
|
232
|
+
fd = int(fileno_fn())
|
|
233
|
+
except Exception: # noqa: BLE001
|
|
234
|
+
fd = -1
|
|
235
|
+
if fd >= 0:
|
|
236
|
+
try:
|
|
237
|
+
ready, _w, _x = select.select([fd], [], [], timeout_s)
|
|
238
|
+
except Exception: # noqa: BLE001
|
|
239
|
+
ready = [fd]
|
|
240
|
+
if not ready:
|
|
241
|
+
return []
|
|
242
|
+
try:
|
|
243
|
+
keys = input_reader.read_keys()
|
|
244
|
+
except Exception: # noqa: BLE001
|
|
245
|
+
return []
|
|
246
|
+
if isinstance(keys, list):
|
|
247
|
+
return keys
|
|
248
|
+
return []
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _run_inline_option_selector(
|
|
252
|
+
*,
|
|
253
|
+
console: Console,
|
|
254
|
+
rows: list[tuple[str, str, str]],
|
|
255
|
+
current_value: str,
|
|
256
|
+
panel_builder: Callable[[str | None, bool], Any],
|
|
257
|
+
unavailable_label: str,
|
|
258
|
+
use_alt_screen: bool = True,
|
|
259
|
+
confirm_on_digit: bool = False,
|
|
260
|
+
) -> tuple[str | None, bool]:
|
|
261
|
+
if _patchable("_is_non_interactive_terminal", _is_non_interactive_terminal)():
|
|
262
|
+
return None, False
|
|
263
|
+
try:
|
|
264
|
+
from prompt_toolkit.input import create_input
|
|
265
|
+
from prompt_toolkit.keys import Keys
|
|
266
|
+
except Exception:
|
|
267
|
+
return None, False
|
|
268
|
+
|
|
269
|
+
if not rows:
|
|
270
|
+
return None, True
|
|
271
|
+
|
|
272
|
+
selected_index = next(
|
|
273
|
+
(idx for idx, (value, _label, _desc) in enumerate(rows) if value == current_value),
|
|
274
|
+
0,
|
|
275
|
+
)
|
|
276
|
+
if selected_index < 0 or selected_index >= len(rows):
|
|
277
|
+
selected_index = 0
|
|
278
|
+
|
|
279
|
+
def _render_panel() -> Any:
|
|
280
|
+
if _terminal_too_small():
|
|
281
|
+
return _terminal_too_small_panel()
|
|
282
|
+
return panel_builder(rows[selected_index][0], True)
|
|
283
|
+
|
|
284
|
+
narrow_token = _PICKER_NARROW_OVERRIDE.set(_picker_is_narrow_terminal())
|
|
285
|
+
input_reader = None
|
|
286
|
+
try:
|
|
287
|
+
input_reader = create_input()
|
|
288
|
+
with _watch_terminal_resize() as consume_resize:
|
|
289
|
+
with _Live(
|
|
290
|
+
_render_panel(),
|
|
291
|
+
console=console,
|
|
292
|
+
auto_refresh=False,
|
|
293
|
+
transient=not use_alt_screen,
|
|
294
|
+
screen=use_alt_screen,
|
|
295
|
+
) as live:
|
|
296
|
+
with input_reader.raw_mode():
|
|
297
|
+
while True:
|
|
298
|
+
resized = consume_resize()
|
|
299
|
+
if resized:
|
|
300
|
+
_clear_terminal_screen(console=console)
|
|
301
|
+
live.update(_render_panel(), refresh=True)
|
|
302
|
+
|
|
303
|
+
key_presses = _read_input_keys_with_timeout(
|
|
304
|
+
input_reader=input_reader,
|
|
305
|
+
timeout_s=0.12,
|
|
306
|
+
)
|
|
307
|
+
if not key_presses:
|
|
308
|
+
continue
|
|
309
|
+
panel_updated = False
|
|
310
|
+
for key_press in key_presses:
|
|
311
|
+
key = key_press.key
|
|
312
|
+
raw_data = key_press.data
|
|
313
|
+
if _terminal_too_small():
|
|
314
|
+
if (
|
|
315
|
+
key == Keys.Escape
|
|
316
|
+
or key == Keys.ControlC
|
|
317
|
+
or key
|
|
318
|
+
in {
|
|
319
|
+
"escape",
|
|
320
|
+
"c-c",
|
|
321
|
+
}
|
|
322
|
+
):
|
|
323
|
+
return None, True
|
|
324
|
+
continue
|
|
325
|
+
|
|
326
|
+
if key == Keys.Up or key == "up":
|
|
327
|
+
selected_index = (selected_index - 1) % len(rows)
|
|
328
|
+
panel_updated = True
|
|
329
|
+
continue
|
|
330
|
+
if key == Keys.Down or key == "down":
|
|
331
|
+
selected_index = (selected_index + 1) % len(rows)
|
|
332
|
+
panel_updated = True
|
|
333
|
+
continue
|
|
334
|
+
digit_key: str | None = None
|
|
335
|
+
if isinstance(key, str) and key.isdigit():
|
|
336
|
+
digit_key = key
|
|
337
|
+
elif isinstance(raw_data, str) and raw_data.isdigit():
|
|
338
|
+
digit_key = raw_data
|
|
339
|
+
if digit_key is not None:
|
|
340
|
+
idx = int(digit_key) - 1
|
|
341
|
+
if 0 <= idx < len(rows):
|
|
342
|
+
selected_index = idx
|
|
343
|
+
if confirm_on_digit:
|
|
344
|
+
return rows[selected_index][0], True
|
|
345
|
+
panel_updated = True
|
|
346
|
+
continue
|
|
347
|
+
if (
|
|
348
|
+
key == Keys.Enter
|
|
349
|
+
or key == Keys.ControlM
|
|
350
|
+
or raw_data in {chr(13), chr(10)}
|
|
351
|
+
):
|
|
352
|
+
return rows[selected_index][0], True
|
|
353
|
+
if (
|
|
354
|
+
key == Keys.Escape
|
|
355
|
+
or key == Keys.ControlC
|
|
356
|
+
or key
|
|
357
|
+
in {
|
|
358
|
+
"escape",
|
|
359
|
+
"c-c",
|
|
360
|
+
}
|
|
361
|
+
):
|
|
362
|
+
return None, True
|
|
363
|
+
if panel_updated:
|
|
364
|
+
live.update(_render_panel(), refresh=True)
|
|
365
|
+
except Exception as e: # noqa: BLE001
|
|
366
|
+
console.print(f"[yellow]{unavailable_label} unavailable:[/yellow] {e}")
|
|
367
|
+
return None, False
|
|
368
|
+
finally:
|
|
369
|
+
_PICKER_NARROW_OVERRIDE.reset(narrow_token)
|
|
370
|
+
close = getattr(input_reader, "close", None)
|
|
371
|
+
if callable(close):
|
|
372
|
+
close()
|
|
373
|
+
return None, True
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _guarded_workspace_panel(
|
|
377
|
+
*,
|
|
378
|
+
binding: WorkspaceBinding,
|
|
379
|
+
candidates: tuple[WorkspaceCandidate, ...],
|
|
380
|
+
allow_use_current_action: bool = True,
|
|
381
|
+
selected_action: str | None = None,
|
|
382
|
+
interactive: bool = False,
|
|
383
|
+
) -> Panel:
|
|
384
|
+
path_label = os.fspath(binding.requested_path)
|
|
385
|
+
title = f"Guarded Workspace: {path_label}"
|
|
386
|
+
return _selectable_options_panel(
|
|
387
|
+
title=title,
|
|
388
|
+
rows=guarded_workspace_action_rows(
|
|
389
|
+
binding=binding,
|
|
390
|
+
candidates=candidates,
|
|
391
|
+
allow_use_current_action=allow_use_current_action,
|
|
392
|
+
),
|
|
393
|
+
selected_value=selected_action,
|
|
394
|
+
interactive=interactive,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _select_guarded_workspace_action_interactive(
|
|
399
|
+
*,
|
|
400
|
+
binding: WorkspaceBinding,
|
|
401
|
+
candidates: tuple[WorkspaceCandidate, ...],
|
|
402
|
+
allow_use_current_action: bool = True,
|
|
403
|
+
console: Console,
|
|
404
|
+
) -> tuple[str | None, bool]:
|
|
405
|
+
rows = guarded_workspace_action_rows(
|
|
406
|
+
binding=binding,
|
|
407
|
+
candidates=candidates,
|
|
408
|
+
allow_use_current_action=allow_use_current_action,
|
|
409
|
+
)
|
|
410
|
+
default_value = rows[1][0] if allow_use_current_action and len(rows) > 1 else rows[0][0]
|
|
411
|
+
return _patchable("_run_inline_option_selector", _run_inline_option_selector)(
|
|
412
|
+
console=console,
|
|
413
|
+
rows=rows,
|
|
414
|
+
current_value=default_value,
|
|
415
|
+
panel_builder=lambda selected, interactive: _guarded_workspace_panel(
|
|
416
|
+
binding=binding,
|
|
417
|
+
candidates=candidates,
|
|
418
|
+
allow_use_current_action=allow_use_current_action,
|
|
419
|
+
selected_action=selected,
|
|
420
|
+
interactive=interactive,
|
|
421
|
+
),
|
|
422
|
+
unavailable_label="Workspace chooser",
|
|
423
|
+
use_alt_screen=False,
|
|
424
|
+
confirm_on_digit=True,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _workspace_candidate_panel(
|
|
429
|
+
*,
|
|
430
|
+
base_path: Path,
|
|
431
|
+
candidates: tuple[WorkspaceCandidate, ...],
|
|
432
|
+
selected_path: Path | None = None,
|
|
433
|
+
interactive: bool = False,
|
|
434
|
+
) -> Panel:
|
|
435
|
+
title = f"Project Folders Under {os.fspath(base_path)}"
|
|
436
|
+
selected_value = os.fspath(selected_path) if selected_path is not None else None
|
|
437
|
+
return _selectable_options_panel(
|
|
438
|
+
title=title,
|
|
439
|
+
rows=workspace_candidate_rows(base_path=base_path, candidates=candidates),
|
|
440
|
+
selected_value=selected_value,
|
|
441
|
+
interactive=interactive,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _select_workspace_candidate_interactive(
|
|
446
|
+
*,
|
|
447
|
+
base_path: Path,
|
|
448
|
+
candidates: tuple[WorkspaceCandidate, ...],
|
|
449
|
+
console: Console,
|
|
450
|
+
) -> tuple[Path | None, bool]:
|
|
451
|
+
rows = workspace_candidate_rows(base_path=base_path, candidates=candidates)
|
|
452
|
+
if not rows:
|
|
453
|
+
return None, True
|
|
454
|
+
selected_value, interactive_available = _patchable(
|
|
455
|
+
"_run_inline_option_selector", _run_inline_option_selector
|
|
456
|
+
)(
|
|
457
|
+
console=console,
|
|
458
|
+
rows=rows,
|
|
459
|
+
current_value=rows[0][0],
|
|
460
|
+
panel_builder=lambda selected, interactive: _workspace_candidate_panel(
|
|
461
|
+
base_path=base_path,
|
|
462
|
+
candidates=candidates,
|
|
463
|
+
selected_path=(Path(selected) if selected is not None else None),
|
|
464
|
+
interactive=interactive,
|
|
465
|
+
),
|
|
466
|
+
unavailable_label="Workspace candidate picker",
|
|
467
|
+
use_alt_screen=False,
|
|
468
|
+
confirm_on_digit=True,
|
|
469
|
+
)
|
|
470
|
+
if selected_value is None:
|
|
471
|
+
return None, interactive_available
|
|
472
|
+
return Path(selected_value), interactive_available
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _guarded_workspace_prompt_text(text: str, *, default: str | None = None) -> str:
|
|
476
|
+
return _patchable("_prompt_text_with_escape", _prompt_text_with_escape)(
|
|
477
|
+
text,
|
|
478
|
+
default=default,
|
|
479
|
+
escape_hint="Esc to go back",
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _prompt_text_with_escape(
|
|
484
|
+
text: str,
|
|
485
|
+
*,
|
|
486
|
+
default: str | None = None,
|
|
487
|
+
escape_hint: str = "Esc to go back",
|
|
488
|
+
) -> str:
|
|
489
|
+
if _patchable("_is_non_interactive_terminal", _is_non_interactive_terminal)():
|
|
490
|
+
return typer.prompt(text, default=default)
|
|
491
|
+
try:
|
|
492
|
+
from prompt_toolkit import PromptSession
|
|
493
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
494
|
+
except Exception:
|
|
495
|
+
return typer.prompt(text, default=default)
|
|
496
|
+
|
|
497
|
+
kb = KeyBindings()
|
|
498
|
+
|
|
499
|
+
@kb.add("escape")
|
|
500
|
+
def _go_back(event: Any) -> None:
|
|
501
|
+
event.app.exit(exception=KeyboardInterrupt())
|
|
502
|
+
|
|
503
|
+
prompt_session = PromptSession(key_bindings=kb)
|
|
504
|
+
prompt_label = f"{text} ({escape_hint})" if escape_hint else text
|
|
505
|
+
return prompt_session.prompt(
|
|
506
|
+
f"{prompt_label}: ",
|
|
507
|
+
default=default if default is not None else "",
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def _resolve_startup_workspace_binding(
|
|
512
|
+
*,
|
|
513
|
+
requested_path: Path,
|
|
514
|
+
console: Console,
|
|
515
|
+
interactive: bool,
|
|
516
|
+
create_if_missing: bool = False,
|
|
517
|
+
allow_broad_workspace: bool = False,
|
|
518
|
+
source: str = "explicit_path",
|
|
519
|
+
action: str = WorkspaceAction.CHAT,
|
|
520
|
+
) -> WorkspaceBinding:
|
|
521
|
+
default_select_action = _select_guarded_workspace_action_interactive
|
|
522
|
+
default_select_candidate = _select_workspace_candidate_interactive
|
|
523
|
+
default_prompt = _guarded_workspace_prompt_text
|
|
524
|
+
select_action = _patchable(
|
|
525
|
+
"_select_guarded_workspace_action_interactive",
|
|
526
|
+
default_select_action,
|
|
527
|
+
)
|
|
528
|
+
select_candidate = _patchable(
|
|
529
|
+
"_select_workspace_candidate_interactive",
|
|
530
|
+
default_select_candidate,
|
|
531
|
+
)
|
|
532
|
+
prompt = _patchable("_guarded_workspace_prompt_text", default_prompt)
|
|
533
|
+
# When the full-screen TUI is active, render the guarded-workspace steps with
|
|
534
|
+
# the TUI's own picker/prompt chrome instead of the classic inline ones, so the
|
|
535
|
+
# startup screen matches the rest of the experience. The binding *logic* is
|
|
536
|
+
# unchanged — only these display callbacks swap. Any import/terminal failure
|
|
537
|
+
# falls back to the classic selectors above.
|
|
538
|
+
if interactive:
|
|
539
|
+
try:
|
|
540
|
+
from ..tui import is_tui_enabled as _tui_enabled
|
|
541
|
+
|
|
542
|
+
if _tui_enabled():
|
|
543
|
+
from ..tui.workspace_guard import (
|
|
544
|
+
select_guarded_workspace_action as _tui_select_action,
|
|
545
|
+
select_workspace_candidate as _tui_select_candidate,
|
|
546
|
+
workspace_guard_prompt_text as _tui_prompt,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
if select_action is default_select_action:
|
|
550
|
+
select_action = _tui_select_action
|
|
551
|
+
if select_candidate is default_select_candidate:
|
|
552
|
+
select_candidate = _tui_select_candidate
|
|
553
|
+
if prompt is default_prompt:
|
|
554
|
+
prompt = _tui_prompt
|
|
555
|
+
except Exception:
|
|
556
|
+
pass
|
|
557
|
+
return _resolve_startup_workspace_binding_impl(
|
|
558
|
+
requested_path=requested_path,
|
|
559
|
+
interactive=interactive,
|
|
560
|
+
create_if_missing=create_if_missing,
|
|
561
|
+
allow_broad_workspace=allow_broad_workspace,
|
|
562
|
+
source=source,
|
|
563
|
+
action=action,
|
|
564
|
+
console=console,
|
|
565
|
+
select_action_interactive=select_action,
|
|
566
|
+
select_candidate_interactive=select_candidate,
|
|
567
|
+
prompt_text=prompt,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def _chat_mode_rows() -> list[tuple[str, str, str]]:
|
|
572
|
+
return [
|
|
573
|
+
("review", "1) safe (review)", "Asks before file writes and shell commands."),
|
|
574
|
+
(
|
|
575
|
+
"auto",
|
|
576
|
+
"2) fast (auto)",
|
|
577
|
+
"Auto-runs normal operations; approval-gated sensitive actions still ask.",
|
|
578
|
+
),
|
|
579
|
+
(
|
|
580
|
+
"readonly",
|
|
581
|
+
"3) read (readonly)",
|
|
582
|
+
"Read-only analysis with inspection-only tools; no writes, shell, or verification.",
|
|
583
|
+
),
|
|
584
|
+
("fullaccess", "4) full (fullaccess)", "No write/shell safety guards or prompts."),
|
|
585
|
+
]
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _chat_persona_rows(custom: Any = None) -> list[tuple[str, str, str]]:
|
|
589
|
+
rows = [
|
|
590
|
+
("code", "1) code", "Implementation work; keeps your execution mode."),
|
|
591
|
+
("architect", "2) architect", "Plans and designs; writes markdown documents only."),
|
|
592
|
+
("ask", "3) ask", "Read-only questions with inspection tools."),
|
|
593
|
+
("debug", "4) debug", "Reproduce-before-fix; keeps your execution mode."),
|
|
594
|
+
]
|
|
595
|
+
if custom:
|
|
596
|
+
index = len(rows)
|
|
597
|
+
for name in sorted(custom):
|
|
598
|
+
definition = custom[name]
|
|
599
|
+
index += 1
|
|
600
|
+
rows.append(
|
|
601
|
+
(
|
|
602
|
+
str(name),
|
|
603
|
+
f"{index}) {name} (custom)",
|
|
604
|
+
str(getattr(definition, "description", "") or "Custom persona."),
|
|
605
|
+
)
|
|
606
|
+
)
|
|
607
|
+
return rows
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _chat_persona_panel(
|
|
611
|
+
*,
|
|
612
|
+
current_persona: str,
|
|
613
|
+
selected_persona: str | None = None,
|
|
614
|
+
interactive: bool = False,
|
|
615
|
+
custom: Any = None,
|
|
616
|
+
) -> Panel:
|
|
617
|
+
current = current_persona.strip().lower() or "code"
|
|
618
|
+
return _selectable_options_panel(
|
|
619
|
+
title=f"Persona Options (current: {current})",
|
|
620
|
+
rows=_chat_persona_rows(custom=custom),
|
|
621
|
+
selected_value=selected_persona,
|
|
622
|
+
interactive=interactive,
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _select_chat_persona_interactive(
|
|
627
|
+
*,
|
|
628
|
+
current_persona: str,
|
|
629
|
+
console: Console,
|
|
630
|
+
custom: Any = None,
|
|
631
|
+
) -> tuple[str | None, bool]:
|
|
632
|
+
normalized_current = current_persona.strip().lower() or "code"
|
|
633
|
+
return _patchable("_run_inline_option_selector", _run_inline_option_selector)(
|
|
634
|
+
console=console,
|
|
635
|
+
rows=_chat_persona_rows(custom=custom),
|
|
636
|
+
current_value=normalized_current,
|
|
637
|
+
panel_builder=lambda selected, interactive: _chat_persona_panel(
|
|
638
|
+
current_persona=current_persona,
|
|
639
|
+
selected_persona=selected,
|
|
640
|
+
interactive=interactive,
|
|
641
|
+
),
|
|
642
|
+
unavailable_label="Persona picker",
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def _select_chat_mode_interactive(
|
|
647
|
+
*,
|
|
648
|
+
current_mode: str,
|
|
649
|
+
console: Console,
|
|
650
|
+
) -> tuple[str | None, bool]:
|
|
651
|
+
normalized_current = current_mode.strip().lower()
|
|
652
|
+
if normalized_current not in _CHAT_MODES:
|
|
653
|
+
normalized_current = "review"
|
|
654
|
+
|
|
655
|
+
rows = _chat_mode_rows()
|
|
656
|
+
return _patchable("_run_inline_option_selector", _run_inline_option_selector)(
|
|
657
|
+
console=console,
|
|
658
|
+
rows=rows,
|
|
659
|
+
current_value=normalized_current,
|
|
660
|
+
panel_builder=lambda selected, interactive: _chat_mode_panel(
|
|
661
|
+
current_mode=current_mode,
|
|
662
|
+
selected_mode=selected,
|
|
663
|
+
interactive=interactive,
|
|
664
|
+
),
|
|
665
|
+
unavailable_label="Mode picker",
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def _chat_mode_display(mode: str) -> str:
|
|
670
|
+
normalized = mode.strip().lower()
|
|
671
|
+
if normalized == "review":
|
|
672
|
+
return "safe (review)"
|
|
673
|
+
if normalized == "auto":
|
|
674
|
+
return "fast (auto)"
|
|
675
|
+
if normalized == "readonly":
|
|
676
|
+
return "read (readonly)"
|
|
677
|
+
if normalized == "fullaccess":
|
|
678
|
+
return "full (fullaccess)"
|
|
679
|
+
return normalized or "unknown"
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def _print_fullaccess_mode_warning(*, console: Console) -> None:
|
|
683
|
+
console.print(
|
|
684
|
+
"[yellow]Warning:[/yellow] full (fullaccess) disables write/shell safety guards "
|
|
685
|
+
"and approval prompts."
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def _chat_mode_panel(
|
|
690
|
+
*,
|
|
691
|
+
current_mode: str,
|
|
692
|
+
selected_mode: str | None = None,
|
|
693
|
+
interactive: bool = False,
|
|
694
|
+
) -> Panel:
|
|
695
|
+
current = current_mode.strip().lower()
|
|
696
|
+
title = f"Mode Options (current: {_chat_mode_display(current)})"
|
|
697
|
+
return _selectable_options_panel(
|
|
698
|
+
title=title,
|
|
699
|
+
rows=_chat_mode_rows(),
|
|
700
|
+
selected_value=selected_mode,
|
|
701
|
+
interactive=interactive,
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _chat_trace_rows() -> list[tuple[str, str, str]]:
|
|
706
|
+
return [
|
|
707
|
+
("off", "1) off", "Hide reasoning summaries and routine activity."),
|
|
708
|
+
(
|
|
709
|
+
"compact",
|
|
710
|
+
"2) compact",
|
|
711
|
+
"Collapsed provider summaries and key activity (recommended).",
|
|
712
|
+
),
|
|
713
|
+
(
|
|
714
|
+
"full",
|
|
715
|
+
"3) full",
|
|
716
|
+
"Expanded provider summaries and detailed tool activity; never raw thinking.",
|
|
717
|
+
),
|
|
718
|
+
]
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def _chat_trace_panel(
|
|
722
|
+
*,
|
|
723
|
+
current_level: str,
|
|
724
|
+
selected_level: str | None = None,
|
|
725
|
+
interactive: bool = False,
|
|
726
|
+
) -> Panel:
|
|
727
|
+
current = _resolve_trace_level(current_level) or "compact"
|
|
728
|
+
title = f"Trace Options (current: {current})"
|
|
729
|
+
return _selectable_options_panel(
|
|
730
|
+
title=title,
|
|
731
|
+
rows=_chat_trace_rows(),
|
|
732
|
+
selected_value=selected_level,
|
|
733
|
+
interactive=interactive,
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def _select_chat_trace_interactive(
|
|
738
|
+
*,
|
|
739
|
+
current_level: str,
|
|
740
|
+
console: Console,
|
|
741
|
+
) -> tuple[str | None, bool]:
|
|
742
|
+
normalized_current = _resolve_trace_level(current_level) or "compact"
|
|
743
|
+
rows = _chat_trace_rows()
|
|
744
|
+
return _patchable("_run_inline_option_selector", _run_inline_option_selector)(
|
|
745
|
+
console=console,
|
|
746
|
+
rows=rows,
|
|
747
|
+
current_value=normalized_current,
|
|
748
|
+
panel_builder=lambda selected, interactive: _chat_trace_panel(
|
|
749
|
+
current_level=normalized_current,
|
|
750
|
+
selected_level=selected,
|
|
751
|
+
interactive=interactive,
|
|
752
|
+
),
|
|
753
|
+
unavailable_label="Trace picker",
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
def _chat_usage_hud_rows() -> list[tuple[str, str, str]]:
|
|
758
|
+
return [
|
|
759
|
+
("on", "1) on", "Show usage summary continuously in the toolbar."),
|
|
760
|
+
("off", "2) off", "Hide usage summary from the toolbar."),
|
|
761
|
+
("status", "3) status", "Show current HUD setting without changing it."),
|
|
762
|
+
]
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def _chat_usage_hud_panel(
|
|
766
|
+
*,
|
|
767
|
+
current_enabled: bool,
|
|
768
|
+
selected_action: str | None = None,
|
|
769
|
+
interactive: bool = False,
|
|
770
|
+
) -> Panel:
|
|
771
|
+
current = "on" if current_enabled else "off"
|
|
772
|
+
title = f"Usage HUD Options (current: {current})"
|
|
773
|
+
return _selectable_options_panel(
|
|
774
|
+
title=title,
|
|
775
|
+
rows=_chat_usage_hud_rows(),
|
|
776
|
+
selected_value=selected_action,
|
|
777
|
+
interactive=interactive,
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def _select_chat_usage_hud_interactive(
|
|
782
|
+
*,
|
|
783
|
+
current_enabled: bool,
|
|
784
|
+
console: Console,
|
|
785
|
+
) -> tuple[str | None, bool]:
|
|
786
|
+
rows = _chat_usage_hud_rows()
|
|
787
|
+
return _patchable("_run_inline_option_selector", _run_inline_option_selector)(
|
|
788
|
+
console=console,
|
|
789
|
+
rows=rows,
|
|
790
|
+
current_value="on" if current_enabled else "off",
|
|
791
|
+
panel_builder=lambda selected, interactive: _chat_usage_hud_panel(
|
|
792
|
+
current_enabled=current_enabled,
|
|
793
|
+
selected_action=selected,
|
|
794
|
+
interactive=interactive,
|
|
795
|
+
),
|
|
796
|
+
unavailable_label="Usage HUD picker",
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def _forge_assistant_rows() -> list[tuple[str, str, str]]:
|
|
801
|
+
return [
|
|
802
|
+
("on", "1) on", "Enable planner assistant suggestions."),
|
|
803
|
+
("off", "2) off", "Disable planner assistant suggestions."),
|
|
804
|
+
("status", "3) status", "Show assistant status without changing it."),
|
|
805
|
+
]
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def _forge_assistant_panel(
|
|
809
|
+
*,
|
|
810
|
+
enabled: bool,
|
|
811
|
+
selected_action: str | None = None,
|
|
812
|
+
interactive: bool = False,
|
|
813
|
+
) -> Panel:
|
|
814
|
+
current = "ON" if enabled else "OFF"
|
|
815
|
+
title = f"Planner Assistant (current: {current})"
|
|
816
|
+
return _selectable_options_panel(
|
|
817
|
+
title=title,
|
|
818
|
+
rows=_forge_assistant_rows(),
|
|
819
|
+
selected_value=selected_action,
|
|
820
|
+
interactive=interactive,
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def _select_forge_assistant_interactive(
|
|
825
|
+
*,
|
|
826
|
+
enabled: bool,
|
|
827
|
+
console: Console,
|
|
828
|
+
) -> tuple[str | None, bool]:
|
|
829
|
+
rows = _forge_assistant_rows()
|
|
830
|
+
return _patchable("_run_inline_option_selector", _run_inline_option_selector)(
|
|
831
|
+
console=console,
|
|
832
|
+
rows=rows,
|
|
833
|
+
current_value="on" if enabled else "off",
|
|
834
|
+
panel_builder=lambda selected, interactive: _forge_assistant_panel(
|
|
835
|
+
enabled=enabled,
|
|
836
|
+
selected_action=selected,
|
|
837
|
+
interactive=interactive,
|
|
838
|
+
),
|
|
839
|
+
unavailable_label="Assistant picker",
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def _forge_entry_plan_assistant_rows() -> list[tuple[str, str, str]]:
|
|
844
|
+
return [
|
|
845
|
+
("yes", "1) Yes", "Enable planner assistant now."),
|
|
846
|
+
("no", "2) No", "Keep assistant off and provide plan text manually."),
|
|
847
|
+
]
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
def _forge_entry_plan_assistant_panel(
|
|
851
|
+
*,
|
|
852
|
+
selected_action: str | None = None,
|
|
853
|
+
interactive: bool = False,
|
|
854
|
+
) -> Panel:
|
|
855
|
+
return _selectable_options_panel(
|
|
856
|
+
title="Do you want Plan Assistant?",
|
|
857
|
+
rows=_forge_entry_plan_assistant_rows(),
|
|
858
|
+
selected_value=selected_action,
|
|
859
|
+
interactive=interactive,
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def _select_forge_entry_plan_assistant_interactive(*, console: Console) -> tuple[str | None, bool]:
|
|
864
|
+
rows = _forge_entry_plan_assistant_rows()
|
|
865
|
+
return _patchable("_run_inline_option_selector", _run_inline_option_selector)(
|
|
866
|
+
console=console,
|
|
867
|
+
rows=rows,
|
|
868
|
+
current_value="yes",
|
|
869
|
+
panel_builder=lambda selected, interactive: _forge_entry_plan_assistant_panel(
|
|
870
|
+
selected_action=selected,
|
|
871
|
+
interactive=interactive,
|
|
872
|
+
),
|
|
873
|
+
unavailable_label="Plan assistant chooser",
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def _prompt_forge_entry_plan_assistant(*, console: Console) -> bool:
|
|
878
|
+
env_override = env_get("ALYSIS_FORGE_PLAN_ASSISTANT")
|
|
879
|
+
if env_override is not None:
|
|
880
|
+
parsed_override = _parse_bool_text(env_override)
|
|
881
|
+
if parsed_override is None:
|
|
882
|
+
console.print(
|
|
883
|
+
"[yellow]Invalid ALYSIS_FORGE_PLAN_ASSISTANT value; defaulting to OFF.[/yellow]"
|
|
884
|
+
)
|
|
885
|
+
return False
|
|
886
|
+
return parsed_override
|
|
887
|
+
|
|
888
|
+
if _patchable("_is_non_interactive_terminal", _is_non_interactive_terminal)():
|
|
889
|
+
return False
|
|
890
|
+
|
|
891
|
+
action, picker_available = _select_forge_entry_plan_assistant_interactive(console=console)
|
|
892
|
+
if picker_available:
|
|
893
|
+
return action == "yes"
|
|
894
|
+
|
|
895
|
+
try:
|
|
896
|
+
fallback = typer.prompt("Do you want Plan Assistant? [y/N]", default="n").strip()
|
|
897
|
+
except (EOFError, KeyboardInterrupt):
|
|
898
|
+
console.print("")
|
|
899
|
+
return False
|
|
900
|
+
parsed = _parse_bool_text(fallback)
|
|
901
|
+
if parsed is None:
|
|
902
|
+
console.print("[yellow]Invalid selection; defaulting to No.[/yellow]")
|
|
903
|
+
return False
|
|
904
|
+
return parsed
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def _forge_plan_command_rows() -> list[tuple[str, str, str]]:
|
|
908
|
+
return [
|
|
909
|
+
("tasks", "1) show summary", "Show the current plan summary and task list."),
|
|
910
|
+
("markdown", "2) markdown", "Render PLAN.md for the current run."),
|
|
911
|
+
("edit", "3) edit", "Edit plan.json and reload it into Forge."),
|
|
912
|
+
]
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def _forge_plan_command_panel(
|
|
916
|
+
*,
|
|
917
|
+
selected_action: str | None = None,
|
|
918
|
+
interactive: bool = False,
|
|
919
|
+
) -> Panel:
|
|
920
|
+
return _selectable_options_panel(
|
|
921
|
+
title="Forge Plan",
|
|
922
|
+
rows=_forge_plan_command_rows(),
|
|
923
|
+
selected_value=selected_action,
|
|
924
|
+
interactive=interactive,
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def _select_forge_plan_command_interactive(*, console: Console) -> tuple[str | None, bool]:
|
|
929
|
+
rows = _forge_plan_command_rows()
|
|
930
|
+
return _patchable("_run_inline_option_selector", _run_inline_option_selector)(
|
|
931
|
+
console=console,
|
|
932
|
+
rows=rows,
|
|
933
|
+
current_value="tasks",
|
|
934
|
+
panel_builder=lambda selected, interactive: _forge_plan_command_panel(
|
|
935
|
+
selected_action=selected,
|
|
936
|
+
interactive=interactive,
|
|
937
|
+
),
|
|
938
|
+
unavailable_label="Forge plan command picker",
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
__all__ = [name for name in globals() if (not name.startswith("__") or name == "__version__")]
|