drydock-cli 0.6.3__tar.gz → 0.6.5__tar.gz
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.
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/PKG-INFO +1 -1
- drydock_cli-0.6.5/PRD.md +180 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/pyproject.toml +1 -1
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/scripts/deploy_to_github.sh +1 -1
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/scripts/publish_to_pypi.sh +1 -1
- drydock_cli-0.6.5/tests/test_loop_detection.py +398 -0
- drydock_cli-0.6.3/PRD.md +0 -268
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.github/CODEOWNERS +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.github/DISCUSSION_TEMPLATE/ideas.yml +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.github/workflows/build-and-upload.yml +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.github/workflows/ci.yml +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.github/workflows/issue-labeler.yml +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.github/workflows/release.yml +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.gitignore +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.pre-commit-config.yaml +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.python-version +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.typos.toml +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.vscode/extensions.json +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.vscode/launch.json +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/.vscode/settings.json +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/AGENTS.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/CHANGELOG.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/CLAUDE.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/CONTRIBUTING.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/Drydock_rebrand.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/LICENSE +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/NOTICE +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/README.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/action.yml +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/distribution/zed/LICENSE +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/distribution/zed/extension.toml +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/distribution/zed/icons/mistral_vibe.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/docs/README.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/docs/acp-setup.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/docs/proxy-setup.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/acp_agent_loop.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/acp_logger.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/entrypoint.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/tools/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/tools/base.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/tools/builtins/bash.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/tools/builtins/read_file.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/tools/builtins/search_replace.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/tools/builtins/todo.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/tools/builtins/write_file.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/tools/session_update.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/acp/utils.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/autocompletion/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/autocompletion/base.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/autocompletion/path_completion.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/autocompletion/slash_command.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/cli.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/clipboard.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/commands.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/entrypoint.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/history_manager.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/plan_offer/adapters/http_whoami_gateway.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/plan_offer/decide_plan_offer.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/plan_offer/ports/whoami_gateway.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/terminal_setup.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/ansi_markdown.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/app.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/app.tcss +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/external_editor.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/handlers/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/handlers/event_handler.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/notifications/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/notifications/adapters/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/notifications/adapters/textual_notification_adapter.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/notifications/ports/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/notifications/ports/notification_port.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/approval_app.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/banner/banner.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/banner/petit_chat.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/braille_renderer.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/chat_input/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/chat_input/body.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/chat_input/completion_manager.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/chat_input/completion_popup.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/chat_input/container.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/chat_input/text_area.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/compact.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/config_app.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/context_progress.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/load_more.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/loading.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/messages.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/no_markup_static.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/path_display.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/proxy_setup_app.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/question_app.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/session_picker.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/spinner.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/status_message.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/teleport_message.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/tool_widgets.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/tools.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/widgets/vscode_compat.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/windowing/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/windowing/history.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/windowing/history_windowing.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/textual_ui/windowing/state.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/update_notifier/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/update_notifier/adapters/filesystem_update_cache_repository.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/update_notifier/adapters/github_update_gateway.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/update_notifier/adapters/pypi_update_gateway.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/update_notifier/ports/update_cache_repository.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/update_notifier/ports/update_gateway.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/update_notifier/update.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/cli/update_notifier/whats_new.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/agent_loop.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/agents/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/agents/manager.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/agents/models.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/auth/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/auth/crypto.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/auth/github.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/autocompletion/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/autocompletion/completers.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/autocompletion/file_indexer/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/autocompletion/file_indexer/ignore_rules.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/autocompletion/file_indexer/indexer.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/autocompletion/file_indexer/store.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/autocompletion/file_indexer/watcher.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/autocompletion/fuzzy.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/autocompletion/path_prompt.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/autocompletion/path_prompt_adapter.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/config/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/config/_settings.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/config/harness_files/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/config/harness_files/_harness_manager.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/config/harness_files/_paths.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/consultant.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/drydock_states.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/llm/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/llm/backend/anthropic.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/llm/backend/base.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/llm/backend/factory.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/llm/backend/generic.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/llm/backend/mistral.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/llm/backend/reasoning_adapter.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/llm/backend/vertex.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/llm/exceptions.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/llm/format.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/llm/message_utils.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/llm/types.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/logger.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/middleware.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/output_formatters.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/paths/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/paths/_local_config_walk.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/paths/_vibe_home.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/paths/conventions.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/plan_session.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/programmatic.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/prompts/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/prompts/cli.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/prompts/compact.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/prompts/dangerous_directory.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/prompts/diagnostic.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/prompts/explore.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/prompts/planner.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/prompts/project_context.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/prompts/tests.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/proxy_setup.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/session/session_loader.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/session/session_logger.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/session/session_migration.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/session/state_file.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/skills/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/skills/manager.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/skills/models.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/skills/parser.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/slug.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/system_prompt.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/telemetry/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/telemetry/send.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/teleport/errors.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/teleport/git.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/teleport/nuage.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/teleport/teleport.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/teleport/types.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/base.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/ask_user_question.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/bash.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/exit_plan_mode.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/grep.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/prompts/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/prompts/ask_user_question.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/prompts/bash.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/prompts/grep.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/prompts/read_file.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/prompts/search_replace.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/prompts/swe_bench.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/prompts/task.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/prompts/todo.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/prompts/webfetch.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/prompts/websearch.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/prompts/write_file.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/read_file.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/search_replace.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/task.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/todo.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/webfetch.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/websearch.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/builtins/write_file.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/injection_guard.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/manager.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/mcp/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/mcp/registry.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/mcp/tools.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/mcp_sampling.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/ui.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/tools/utils.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/trusted_folders.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/types.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/core/utils.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/setup/onboarding/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/setup/onboarding/base.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/setup/onboarding/onboarding.tcss +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/setup/onboarding/screens/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/setup/onboarding/screens/api_key.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/setup/onboarding/screens/welcome.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/setup/trusted_folders/trust_folder_dialog.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/setup/trusted_folders/trust_folder_dialog.tcss +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/skills/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/skills/create-presentation/SKILL.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock/whats_new.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock-acp.spec +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/drydock_terms.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/flake.lock +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/flake.nix +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/scripts/README.md +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/scripts/bump_version.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/scripts/install.sh +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/scripts/prepare_release.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/conftest.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_acp.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_agent_thought.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_bash.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_compact_session_updates.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_content.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_initialize.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_list_sessions.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_load_session.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_multi_session.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_new_session.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_proxy_setup_acp.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_read_file.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_search_replace.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_set_config_option.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_set_mode.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_set_model.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_tool_call_session_update.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_utils.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/acp/test_write_file.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/autocompletion/test_file_indexer.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/autocompletion/test_fuzzy.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/autocompletion/test_path_completer_fuzzy.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/autocompletion/test_path_completer_recursive.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/autocompletion/test_path_completion_controller.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/autocompletion/test_path_prompt_transformer.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/autocompletion/test_slash_command_controller.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/autocompletion/test_ui_chat_autocompletion.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/backend/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/backend/data/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/backend/data/fireworks.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/backend/data/mistral.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/backend/test_anthropic_adapter.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/backend/test_backend.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/backend/test_reasoning_adapter.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/backend/test_vertex_anthropic_adapter.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/plan_offer/adapters/fake_whoami_gateway.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/plan_offer/test_decide_plan_offer.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/plan_offer/test_http_whoami_gateway.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_bell_notifications.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_braille_renderer.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_clipboard.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_commands.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_copy_shortcuts.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_external_editor.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_no_markup_static.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_question_app.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_spinner.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_switching_mode.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_ui_clipboard_notifications.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_ui_session_incremental_renderer.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_ui_session_resume.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/test_ui_skill_dispatch.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/textual_ui/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/cli/textual_ui/test_session_picker.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/conftest.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_agents.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_auth_crypto.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_auth_github.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_config_load_dotenv.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_config_paths.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_config_resolution.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_file_logging.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_plan_session.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_proxy_setup.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_slug.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_telemetry_send.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_teleport_git.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_teleport_nuage.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_teleport_service.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_trusted_folders.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/core/test_utils.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/e2e/common.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/e2e/conftest.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/e2e/mock_server.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/e2e/test_cli_tui_onboarding.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/e2e/test_cli_tui_streaming.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/e2e/test_cli_tui_tool_approval.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/mock/__init__.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/mock/mock_backend_factory.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/mock/mock_entrypoint.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/mock/utils.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/onboarding/test_run_onboarding.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/onboarding/test_ui_onboarding.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/session/test_session_loader.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/session/test_session_logger.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/session/test_session_migration.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/skills/conftest.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/skills/test_manager.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/skills/test_models.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/skills/test_parser.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_ask_user_question/test_snapshot_ask_user_question_collapsed.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_ask_user_question/test_snapshot_ask_user_question_expanded.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_basic_conversation/test_snapshot_shows_basic_conversation.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_code_block_horizontal_scrolling/test_snapshot_allows_horizontal_scrolling_for_long_code_blocks.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_empty_assistant_before_reasoning/test_snapshot_empty_assistant_removed_when_reasoning_starts.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_modes/test_snapshot_cycle_to_accept_edits_mode.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_modes/test_snapshot_cycle_to_auto_approve_mode.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_modes/test_snapshot_cycle_to_plan_mode.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_modes/test_snapshot_cycle_wraps_to_default.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_modes/test_snapshot_default_mode.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_parallel_tool_calls/test_snapshot_parallel_tool_calls_pending.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_parallel_tool_calls/test_snapshot_parallel_tool_calls_resolved.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_proxy_setup/test_snapshot_proxy_setup_cancel_discards_changes.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_proxy_setup/test_snapshot_proxy_setup_edit_existing_values.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_proxy_setup/test_snapshot_proxy_setup_initial_empty.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_proxy_setup/test_snapshot_proxy_setup_initial_with_values.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_proxy_setup/test_snapshot_proxy_setup_save_error.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_proxy_setup/test_snapshot_proxy_setup_save_new_values.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_question_answer_first_advance.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_question_first_answered_checkmark.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_question_initial.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_question_navigate_left_wraps.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_question_navigate_right.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_question_tab_to_second.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_select_initial.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_select_mixed_selection.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_select_navigate_to_submit.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_select_other_with_text.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_select_toggle_first.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_select_toggle_multiple.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_multi_select_untoggle.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_question_app_initial.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_question_app_navigate_down.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_question_app_navigate_to_other.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_question_app_navigate_to_third_option.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_question_app_navigate_up_wraps.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_question_app/test_snapshot_question_app_other_typing.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_reasoning_content/test_snapshot_buffered_reasoning_yields_before_content.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_reasoning_content/test_snapshot_shows_interleaved_reasoning.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_reasoning_content/test_snapshot_shows_reasoning_content.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_reasoning_content/test_snapshot_shows_reasoning_content_expanded.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_release_update_notification/test_snapshot_shows_release_update_notification.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_session_resume/test_snapshot_shows_resumed_session_messages.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_streaming_tool_call/test_snapshot_tool_call_partial.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_streaming_tool_call/test_snapshot_tool_call_updated.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_push_confirmation_cancel_selected.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_push_confirmation_multiple_commits.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_push_confirmation_single_commit.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_status_auth_complete.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_status_auth_required.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_status_checking_git.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_status_complete.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_status_error.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_status_pushing.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_status_sending_token.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_status_starting_workflow.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_whats_new/test_snapshot_shows_no_plan_message.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_whats_new/test_snapshot_shows_switch_message.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_whats_new/test_snapshot_shows_upgrade_message.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/__snapshots__/test_ui_snapshot_whats_new/test_snapshot_shows_whats_new_message.svg +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/base_snapshot_test_app.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/conftest.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/snap_compare.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_ask_user_question.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_basic_conversation.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_code_block_horizontal_scrolling.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_empty_assistant_before_reasoning.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_modes.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_parallel_tool_calls.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_proxy_setup.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_question_app.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_reasoning_content.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_release_update_notification.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_session_resume.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_streaming_tool_call.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_teleport.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/snapshots/test_ui_snapshot_whats_new.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/stubs/fake_backend.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/stubs/fake_client.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/stubs/fake_tool.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_agent_auto_compact.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_agent_backend.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_agent_observer_streaming.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_agent_stats.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_agent_tool_call.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_agents.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_cli_programmatic_preload.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_drydock_regression.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_drydock_tasks.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_history_manager.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_message_id.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_message_merging.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_middleware.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_reasoning_content.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_system_prompt.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_tagged_text.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_ui_external_editor.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/test_ui_input_history.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/tools/test_ask_user_question.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/tools/test_bash.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/tools/test_exit_plan_mode.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/tools/test_grep.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/tools/test_invoke_context.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/tools/test_manager_get_tool_config.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/tools/test_mcp.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/tools/test_mcp_sampling.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/tools/test_task.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/tools/test_ui_bash_execution.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/tools/test_webfetch.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/tools/test_websearch.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/update_notifier/adapters/fake_update_cache_repository.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/update_notifier/adapters/fake_update_gateway.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/update_notifier/test_do_update.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/update_notifier/test_filesystem_update_cache_repository.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/update_notifier/test_github_update_gateway.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/update_notifier/test_pypi_update_gateway.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/update_notifier/test_ui_update_notification.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/update_notifier/test_update_use_case.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/tests/update_notifier/test_whats_new.py +0 -0
- {drydock_cli-0.6.3 → drydock_cli-0.6.5}/uv.lock +0 -0
drydock_cli-0.6.5/PRD.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# DryDock — Local CLI Coding Agent
|
|
2
|
+
|
|
3
|
+
**Repository:** https://github.com/fbobe321/drydock
|
|
4
|
+
**PyPI:** https://pypi.org/project/drydock-cli/ (v0.6.3)
|
|
5
|
+
**License:** Apache 2.0 (fork of [mistralai/mistral-vibe](https://github.com/mistralai/mistral-vibe))
|
|
6
|
+
**Status:** Active development — continuous improvement running
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Deployment Process
|
|
11
|
+
|
|
12
|
+
Every change follows this pipeline:
|
|
13
|
+
|
|
14
|
+
1. **Code** → modify files in `drydock/` package directory
|
|
15
|
+
2. **Syntax check** → `python3 -c "import ast; ast.parse(...)"`
|
|
16
|
+
3. **Regression tests** → 81 tests must pass (auto-run by deploy scripts)
|
|
17
|
+
4. **Commit** → descriptive message with Co-Authored-By
|
|
18
|
+
5. **Publish** → `./scripts/publish_to_pypi.sh` (tests → build → PyPI → GitHub)
|
|
19
|
+
|
|
20
|
+
Scripts:
|
|
21
|
+
- `scripts/deploy_to_github.sh` — runs tests, syncs to GitHub. Cron daily at 4 AM.
|
|
22
|
+
- `scripts/publish_to_pypi.sh` — runs tests, bumps version, builds wheel, uploads to PyPI, deploys to GitHub. Aborts on test failure.
|
|
23
|
+
|
|
24
|
+
Both scripts gate on the full 81-test regression suite. No deploy happens if tests fail.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Regression Test Suite (81 tests)
|
|
29
|
+
|
|
30
|
+
Two test files, run in 0.3s:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pytest tests/test_drydock_regression.py tests/test_drydock_tasks.py -p no:xdist -p no:cov --override-ini="addopts="
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**test_drydock_regression.py (37 tests):**
|
|
37
|
+
Message ordering (6), system note injection (3), wave spinner (2), config paths (3), state terms (4), Easter eggs (2), bash allowlist (5), conda detection (2), CLI flags (2), loop thresholds (1), write file safety (3), loop patterns (2), loading widget (2)
|
|
38
|
+
|
|
39
|
+
**test_drydock_tasks.py (44 tests):**
|
|
40
|
+
Binary file guard (5), unknown tool handling (1), loop thresholds (3), file I/O timeouts (4), skill discovery (3), config migration (2), system prompt content (3), bash allowlist (2), wave spinner (3), Easter eggs (1), injection guard (4), state file (4), context warnings (3), deviation rules (1), circuit breaker (2), CLI flags (2), thinking throttle (1)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Continuous Improvement
|
|
45
|
+
|
|
46
|
+
DryDock improves itself automatically and survives restarts:
|
|
47
|
+
|
|
48
|
+
1. **`continuous_bench.sh`** runs SWE-bench batches in a loop (20 tasks, 600s timeout)
|
|
49
|
+
2. **`@reboot` cron** restarts 2 minutes after any system restart
|
|
50
|
+
3. **Every 6 hours** cron re-launches if the loop died
|
|
51
|
+
4. **Daily at 4 AM** deploys to GitHub (with test gate)
|
|
52
|
+
5. **State persists** in `continuous_bench_state.json`
|
|
53
|
+
|
|
54
|
+
**Latest results (Mar 25):**
|
|
55
|
+
- 520 task runs completed, 500/500 unique tasks covered
|
|
56
|
+
- **254/500 passed (50.8%)** — up from 207/500 baseline (41.4%)
|
|
57
|
+
- **+47 net improvement** (117 newly passing, 70 regressions from model non-determinism)
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Objective
|
|
62
|
+
|
|
63
|
+
| | Value |
|
|
64
|
+
|---|---|
|
|
65
|
+
| **Baseline (Mar 15)** | 207/500 (41.4%) |
|
|
66
|
+
| **Current (Mar 25)** | 254/500 (50.8%) |
|
|
67
|
+
| **Net improvement** | +47 tasks (+9.4%) |
|
|
68
|
+
| **Target** | 80%+ |
|
|
69
|
+
| **Hardware** | 2x RTX 4060 Ti 16GB, devstral-24B-AWQ-4bit via vLLM, 128k context |
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Development Progress
|
|
74
|
+
|
|
75
|
+
### Phase 1: Baseline & Analysis (Mar 14–20)
|
|
76
|
+
1,138 task runs across 254 unique SWE-bench Verified tasks. Identified top failure modes: message ordering crashes (9%), wrong file edits (15%), loop kills (28%), prose-only responses (11%).
|
|
77
|
+
|
|
78
|
+
### Phase 2: Core Agent Improvements (Mar 15–20)
|
|
79
|
+
10 features: failure recovery middleware, fuzzy search_replace, grep source-first sorting, smarter loop detection, diagnostic/planner subagents, "never edit tests" rule, SWE-bench workflow prompt, .codeignore, message ordering fix.
|
|
80
|
+
|
|
81
|
+
### Phase 3: Crash Elimination (Mar 23)
|
|
82
|
+
`_sanitize_message_ordering()` safety net, middleware safe injection, MessageList bug fix, loop threshold tuning, forced-edit nudges, ConversationLimitException exit 0. Result: zero crashes.
|
|
83
|
+
|
|
84
|
+
### Phase 4: Conda/Pip & Rebrand (Mar 23)
|
|
85
|
+
Bash tool allowlist (pip, conda, pytest auto-approve), conda environment detection via BASH_ENV, full Mistral Vibe → DryDock rebrand. Published to GitHub.
|
|
86
|
+
|
|
87
|
+
### Phase 5: UX Overhaul (Mar 24)
|
|
88
|
+
Wave spinner, .drydock config dir, double Ctrl-C, --dangerously-skip-permissions, nautical Easter eggs, status throttle, write file timeouts, binary file guard, pptx skill, bash abuse detection, alternating loop detection, progressive budget warnings, ambiguous prompt guard, message queuing, mouse scroll, ocean blue onboarding. 76 regression tests gating deploys.
|
|
89
|
+
|
|
90
|
+
### Phase 5b: Package Rename (Mar 24)
|
|
91
|
+
`vibe/` → `drydock/` directory rename (908 imports, 257 files). Published to PyPI as drydock-cli. Removed `vibe` CLI entry point.
|
|
92
|
+
|
|
93
|
+
### Phase 6: GSD Integration & Performance (Mar 25)
|
|
94
|
+
|
|
95
|
+
Inspired by [get-shit-done](https://github.com/gsd-build/get-shit-done) (41k stars):
|
|
96
|
+
|
|
97
|
+
| Feature | Details |
|
|
98
|
+
|---------|---------|
|
|
99
|
+
| Tiered context warnings | 4 levels at 50/65/75/85% usage, debounced every 5 calls |
|
|
100
|
+
| Prompt injection guard | Detects role overrides, invisible Unicode, hidden instructions |
|
|
101
|
+
| Structured state file | `.drydock/state.md` persists task context across sessions |
|
|
102
|
+
| Deviation handling rules | Auto-fix bugs/imports, ask user for architecture/scope decisions |
|
|
103
|
+
| Circuit breaker | Blocks exact same tool call after 2 attempts with "already attempted" summary |
|
|
104
|
+
| Thinking flicker fix | Status words change every 4s, not every token |
|
|
105
|
+
| Conda env protection | Preserves user's active environment in subprocesses |
|
|
106
|
+
| `--insecure` / `-k` flag | Disables SSL verification for corporate proxies |
|
|
107
|
+
| `/consult` command | Ask a smarter model for advice — response visible to local model |
|
|
108
|
+
| `consultant_model` config | Select consultant from configured models in config.toml |
|
|
109
|
+
| `.vibe` auto-migration | Copies ~/.vibe → ~/.drydock on first run |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Architecture
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
drydock/
|
|
117
|
+
├── PRD.md ← This document
|
|
118
|
+
├── NOTICE ← Apache 2.0 attribution
|
|
119
|
+
├── drydock/
|
|
120
|
+
│ ├── core/
|
|
121
|
+
│ │ ├── agent_loop.py ← Loop detection, circuit breaker, message ordering
|
|
122
|
+
│ │ ├── consultant.py ← /consult command backend (read-only advisor)
|
|
123
|
+
│ │ ├── middleware.py ← Tiered context warnings
|
|
124
|
+
│ │ ├── programmatic.py ← Headless API entry point
|
|
125
|
+
│ │ ├── session/state_file.py ← Cross-session state persistence
|
|
126
|
+
│ │ ├── tools/injection_guard.py ← Prompt injection detection
|
|
127
|
+
│ │ ├── tools/builtins/bash.py ← Shell, conda/pip, allowlist/denylist
|
|
128
|
+
│ │ ├── tools/builtins/search_replace.py ← Fuzzy auto-apply, recovery hints
|
|
129
|
+
│ │ └── prompts/cli.md ← System prompt with deviation rules
|
|
130
|
+
│ ├── cli/
|
|
131
|
+
│ │ ├── entrypoint.py ← CLI flags (--insecure, --consultant, etc.)
|
|
132
|
+
│ │ ├── commands.py ← Slash commands (/consult, /help, etc.)
|
|
133
|
+
│ │ └── textual_ui/app.py ← TUI, message queuing, double Ctrl-C
|
|
134
|
+
│ └── skills/
|
|
135
|
+
│ └── create-presentation/SKILL.md ← Bundled pptx skill
|
|
136
|
+
├── tests/
|
|
137
|
+
│ ├── test_drydock_regression.py ← 37 component tests
|
|
138
|
+
│ └── test_drydock_tasks.py ← 44 behavior tests
|
|
139
|
+
└── scripts/
|
|
140
|
+
├── deploy_to_github.sh ← Test-gated GitHub deploy
|
|
141
|
+
└── publish_to_pypi.sh ← Test-gated PyPI publish
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Key Technical Decisions
|
|
147
|
+
|
|
148
|
+
**Circuit breaker:** Tracks tool call signatures (hash of name + args). After 2 identical calls, blocks execution and returns the cached result with "ALREADY ATTEMPTED" summary and suggestions to try different approaches.
|
|
149
|
+
|
|
150
|
+
**Consultant model:** `/consult` sends a question to a configured model using DryDock's own backend (same providers, same API keys). The consultant never calls tools — it only returns text advice that gets injected into the conversation so the local model can see and act on it.
|
|
151
|
+
|
|
152
|
+
**Tiered context warnings:** 4 warning levels (50%, 65%, 75%, 85% context used). Debounced every 5 tool calls. Messages escalate from "wrap up" to "STOP NOW."
|
|
153
|
+
|
|
154
|
+
**Config migration:** On first run, if `~/.vibe` exists but `~/.drydock` doesn't, auto-copies everything and leaves a `MIGRATED.txt` note.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Lessons Learned
|
|
159
|
+
|
|
160
|
+
1. **Fix bugs before adding features.** The message ordering crash fix was worth more than all subagents combined.
|
|
161
|
+
2. **Non-determinism is real.** Run 500+ tasks to get stable numbers. 20-task batches are noise.
|
|
162
|
+
3. **The model needs hard guardrails, not suggestions.** The circuit breaker (block after 2) works better than warnings (which the model ignores).
|
|
163
|
+
4. **Test everything.** 81 regression tests catch issues before they ship. Gate deploys on tests.
|
|
164
|
+
5. **Users find different bugs than benchmarks.** SWE-bench found crash bugs. Real usage found UX bugs (flicker, loops, wrong config dirs).
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Next Steps
|
|
169
|
+
|
|
170
|
+
### P1: Startup optimization
|
|
171
|
+
20-second delay on launch. Profile imports, lazy-load heavy modules.
|
|
172
|
+
|
|
173
|
+
### P2: Expand to full SWE-bench (2,294 tasks)
|
|
174
|
+
Batch file ready. Switch continuous_bench.sh once Verified is stable.
|
|
175
|
+
|
|
176
|
+
### P3: Task queue UI
|
|
177
|
+
Show the user what the agent has planned in its execution pipeline.
|
|
178
|
+
|
|
179
|
+
### P4: Support more LLM backends
|
|
180
|
+
Test with Claude, GPT-4, Gemini to understand which improvements are model-specific.
|
|
@@ -37,7 +37,7 @@ AUTH_URL="https://${GITHUB_TOKEN}@github.com/fbobe321/drydock.git"
|
|
|
37
37
|
|
|
38
38
|
log "Running regression tests..."
|
|
39
39
|
cd "$DRYDOCK_SRC"
|
|
40
|
-
if ! python3 -m pytest tests/test_drydock_regression.py tests/test_drydock_tasks.py \
|
|
40
|
+
if ! python3 -m pytest tests/test_drydock_regression.py tests/test_drydock_tasks.py tests/test_loop_detection.py \
|
|
41
41
|
-p no:xdist -p no:cov --override-ini="addopts=" -q 2>&1; then
|
|
42
42
|
log "TESTS FAILED — deploy aborted. Fix the tests before deploying."
|
|
43
43
|
exit 1
|
|
@@ -25,7 +25,7 @@ PYPI_TOKEN=$(cat "$TOKEN_FILE")
|
|
|
25
25
|
|
|
26
26
|
# --- Run tests ---
|
|
27
27
|
log "Running regression tests..."
|
|
28
|
-
if ! $PYTHON -m pytest tests/test_drydock_regression.py tests/test_drydock_tasks.py \
|
|
28
|
+
if ! $PYTHON -m pytest tests/test_drydock_regression.py tests/test_drydock_tasks.py tests/test_loop_detection.py \
|
|
29
29
|
-p no:xdist -p no:cov --override-ini="addopts=" -q 2>&1; then
|
|
30
30
|
log "TESTS FAILED — publish aborted."
|
|
31
31
|
exit 1
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"""Comprehensive loop detection tests.
|
|
2
|
+
|
|
3
|
+
Simulates real message histories that trigger each loop detection mechanism.
|
|
4
|
+
These test the exact scenarios users report:
|
|
5
|
+
- Running `ls -ltr` 5+ times (circuit breaker)
|
|
6
|
+
- Exact same grep repeated 8+ times (Check 1: REPEAT_WARNING)
|
|
7
|
+
- 10 consecutive bash calls with different args (Check 2: same-tool)
|
|
8
|
+
- Reading the same file 5+ times with different offsets (Check 3: same-file)
|
|
9
|
+
- Alternating grep→read_file→grep→read_file (Check 4: alternating)
|
|
10
|
+
- Bash abuse: 10+ bash calls without making an edit
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from types import SimpleNamespace
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
from drydock.core.agent_loop import (
|
|
21
|
+
AgentLoop,
|
|
22
|
+
MAX_TOOL_TURNS,
|
|
23
|
+
REPEAT_FORCE_STOP_THRESHOLD,
|
|
24
|
+
REPEAT_WARNING_THRESHOLD,
|
|
25
|
+
)
|
|
26
|
+
from drydock.core.types import LLMMessage, MessageList, Role, ToolCall, FunctionCall
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _make_agent() -> AgentLoop:
|
|
30
|
+
"""Create a minimal AgentLoop for testing loop detection."""
|
|
31
|
+
al = object.__new__(AgentLoop)
|
|
32
|
+
al.messages = MessageList()
|
|
33
|
+
al._tool_call_history = {}
|
|
34
|
+
return al
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _add_tool_call(al: AgentLoop, tool_name: str, arguments: str = "{}") -> None:
|
|
38
|
+
"""Add an assistant message with a tool call + a tool result."""
|
|
39
|
+
al.messages.append(LLMMessage(
|
|
40
|
+
role=Role.assistant,
|
|
41
|
+
content="",
|
|
42
|
+
tool_calls=[ToolCall(
|
|
43
|
+
id=f"call_{len(al.messages)}",
|
|
44
|
+
function=FunctionCall(name=tool_name, arguments=arguments),
|
|
45
|
+
)],
|
|
46
|
+
))
|
|
47
|
+
al.messages.append(LLMMessage(
|
|
48
|
+
role=Role.tool,
|
|
49
|
+
content=f"Result of {tool_name}",
|
|
50
|
+
))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ============================================================================
|
|
54
|
+
# Circuit Breaker Tests
|
|
55
|
+
# ============================================================================
|
|
56
|
+
|
|
57
|
+
class TestCircuitBreaker:
|
|
58
|
+
"""The circuit breaker blocks the EXACT same tool call after 2 attempts."""
|
|
59
|
+
|
|
60
|
+
def test_first_call_allowed(self):
|
|
61
|
+
al = _make_agent()
|
|
62
|
+
tc = SimpleNamespace(tool_name="bash", raw_arguments='{"command":"ls -ltr"}')
|
|
63
|
+
assert al._circuit_breaker_check(tc) is None
|
|
64
|
+
|
|
65
|
+
def test_second_call_allowed(self):
|
|
66
|
+
al = _make_agent()
|
|
67
|
+
tc = SimpleNamespace(tool_name="bash", raw_arguments='{"command":"ls -ltr"}')
|
|
68
|
+
al._circuit_breaker_record(tc, "file1.py\nfile2.py")
|
|
69
|
+
assert al._circuit_breaker_check(tc) is None
|
|
70
|
+
|
|
71
|
+
def test_third_call_blocked(self):
|
|
72
|
+
al = _make_agent()
|
|
73
|
+
tc = SimpleNamespace(tool_name="bash", raw_arguments='{"command":"ls -ltr"}')
|
|
74
|
+
al._circuit_breaker_record(tc, "file1.py\nfile2.py")
|
|
75
|
+
al._circuit_breaker_record(tc, "file1.py\nfile2.py")
|
|
76
|
+
result = al._circuit_breaker_check(tc)
|
|
77
|
+
assert result is not None
|
|
78
|
+
assert "CIRCUIT BREAKER" in result
|
|
79
|
+
|
|
80
|
+
def test_blocked_message_contains_previous_result(self):
|
|
81
|
+
al = _make_agent()
|
|
82
|
+
tc = SimpleNamespace(tool_name="bash", raw_arguments='{"command":"ls -ltr"}')
|
|
83
|
+
al._circuit_breaker_record(tc, "important_file.py")
|
|
84
|
+
al._circuit_breaker_record(tc, "important_file.py")
|
|
85
|
+
result = al._circuit_breaker_check(tc)
|
|
86
|
+
assert "important_file.py" in result
|
|
87
|
+
|
|
88
|
+
def test_blocked_message_contains_alternatives(self):
|
|
89
|
+
al = _make_agent()
|
|
90
|
+
tc = SimpleNamespace(tool_name="bash", raw_arguments='{"command":"ls -ltr"}')
|
|
91
|
+
al._circuit_breaker_record(tc, "output")
|
|
92
|
+
al._circuit_breaker_record(tc, "output")
|
|
93
|
+
result = al._circuit_breaker_check(tc)
|
|
94
|
+
assert "DIFFERENT" in result or "different" in result
|
|
95
|
+
|
|
96
|
+
def test_different_args_not_blocked(self):
|
|
97
|
+
al = _make_agent()
|
|
98
|
+
tc1 = SimpleNamespace(tool_name="bash", raw_arguments='{"command":"ls -ltr"}')
|
|
99
|
+
tc2 = SimpleNamespace(tool_name="bash", raw_arguments='{"command":"ls -la"}')
|
|
100
|
+
al._circuit_breaker_record(tc1, "r1")
|
|
101
|
+
al._circuit_breaker_record(tc1, "r1")
|
|
102
|
+
assert al._circuit_breaker_check(tc1) is not None # Blocked
|
|
103
|
+
assert al._circuit_breaker_check(tc2) is None # Not blocked
|
|
104
|
+
|
|
105
|
+
def test_different_tools_not_blocked(self):
|
|
106
|
+
al = _make_agent()
|
|
107
|
+
tc1 = SimpleNamespace(tool_name="bash", raw_arguments='{"command":"ls"}')
|
|
108
|
+
tc2 = SimpleNamespace(tool_name="grep", raw_arguments='{"pattern":"foo"}')
|
|
109
|
+
al._circuit_breaker_record(tc1, "r1")
|
|
110
|
+
al._circuit_breaker_record(tc1, "r1")
|
|
111
|
+
assert al._circuit_breaker_check(tc1) is not None
|
|
112
|
+
assert al._circuit_breaker_check(tc2) is None
|
|
113
|
+
|
|
114
|
+
def test_already_attempted_summary(self):
|
|
115
|
+
al = _make_agent()
|
|
116
|
+
tc = SimpleNamespace(tool_name="bash", raw_arguments='{"command":"ls"}')
|
|
117
|
+
al._circuit_breaker_record(tc, "output1")
|
|
118
|
+
al._circuit_breaker_record(tc, "output1")
|
|
119
|
+
result = al._circuit_breaker_check(tc)
|
|
120
|
+
assert "ALREADY ATTEMPTED" in result
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ============================================================================
|
|
124
|
+
# Check 1: Exact Same Tool Call Repeated (REPEAT_WARNING_THRESHOLD)
|
|
125
|
+
# ============================================================================
|
|
126
|
+
|
|
127
|
+
class TestExactRepeatDetection:
|
|
128
|
+
"""Detects the same tool call (name + args) repeated N+ times."""
|
|
129
|
+
|
|
130
|
+
def test_no_repeats_returns_none(self):
|
|
131
|
+
al = _make_agent()
|
|
132
|
+
_add_tool_call(al, "bash", '{"command":"ls"}')
|
|
133
|
+
_add_tool_call(al, "grep", '{"pattern":"foo"}')
|
|
134
|
+
_add_tool_call(al, "read_file", '{"path":"x.py"}')
|
|
135
|
+
assert al._check_tool_call_repetition() is None
|
|
136
|
+
|
|
137
|
+
def test_warning_at_threshold(self):
|
|
138
|
+
al = _make_agent()
|
|
139
|
+
args = '{"command":"ls -ltr"}'
|
|
140
|
+
for _ in range(REPEAT_WARNING_THRESHOLD):
|
|
141
|
+
_add_tool_call(al, "bash", args)
|
|
142
|
+
result = al._check_tool_call_repetition()
|
|
143
|
+
assert result is not None
|
|
144
|
+
assert "WARNING" in result
|
|
145
|
+
|
|
146
|
+
def test_below_threshold_returns_none(self):
|
|
147
|
+
al = _make_agent()
|
|
148
|
+
args = '{"command":"ls -ltr"}'
|
|
149
|
+
for _ in range(REPEAT_WARNING_THRESHOLD - 1):
|
|
150
|
+
_add_tool_call(al, "bash", args)
|
|
151
|
+
assert al._check_tool_call_repetition() is None
|
|
152
|
+
|
|
153
|
+
def test_force_stop_at_threshold(self):
|
|
154
|
+
"""25 exact repeats. Check 2 (same-tool at 8) catches it first as WARNING."""
|
|
155
|
+
al = _make_agent()
|
|
156
|
+
args = '{"command":"ls -ltr"}'
|
|
157
|
+
for _ in range(REPEAT_FORCE_STOP_THRESHOLD):
|
|
158
|
+
_add_tool_call(al, "bash", args)
|
|
159
|
+
result = al._check_tool_call_repetition()
|
|
160
|
+
# Same-tool consecutive check (8 for bash) fires before exact-repeat (25)
|
|
161
|
+
assert result is not None
|
|
162
|
+
assert "WARNING" in result or result == "FORCE_STOP"
|
|
163
|
+
|
|
164
|
+
def test_mixed_calls_dont_trigger(self):
|
|
165
|
+
al = _make_agent()
|
|
166
|
+
for i in range(20):
|
|
167
|
+
_add_tool_call(al, "bash", json.dumps({"command": f"cmd_{i}"}))
|
|
168
|
+
# All different args — shouldn't trigger exact repeat
|
|
169
|
+
result = al._check_tool_call_repetition()
|
|
170
|
+
# Might trigger same-tool check but not exact repeat
|
|
171
|
+
if result:
|
|
172
|
+
assert "FORCE_STOP" not in result
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ============================================================================
|
|
176
|
+
# Check 2: Same Tool Consecutive (Different Args)
|
|
177
|
+
# ============================================================================
|
|
178
|
+
|
|
179
|
+
class TestSameToolConsecutive:
|
|
180
|
+
"""Detects the same tool used N+ times in a row with different args."""
|
|
181
|
+
|
|
182
|
+
def test_8_consecutive_bash_triggers_warning(self):
|
|
183
|
+
al = _make_agent()
|
|
184
|
+
for i in range(8):
|
|
185
|
+
_add_tool_call(al, "bash", json.dumps({"command": f"cmd_{i}"}))
|
|
186
|
+
result = al._check_tool_call_repetition()
|
|
187
|
+
assert result is not None
|
|
188
|
+
assert "WARNING" in result
|
|
189
|
+
assert "bash" in result
|
|
190
|
+
|
|
191
|
+
def test_7_consecutive_bash_no_warning(self):
|
|
192
|
+
al = _make_agent()
|
|
193
|
+
for i in range(7):
|
|
194
|
+
_add_tool_call(al, "bash", json.dumps({"command": f"cmd_{i}"}))
|
|
195
|
+
result = al._check_tool_call_repetition()
|
|
196
|
+
assert result is None
|
|
197
|
+
|
|
198
|
+
def test_10_consecutive_grep_triggers_warning(self):
|
|
199
|
+
al = _make_agent()
|
|
200
|
+
for i in range(10):
|
|
201
|
+
_add_tool_call(al, "grep", json.dumps({"pattern": f"pattern_{i}"}))
|
|
202
|
+
result = al._check_tool_call_repetition()
|
|
203
|
+
assert result is not None
|
|
204
|
+
assert "WARNING" in result
|
|
205
|
+
|
|
206
|
+
def test_9_consecutive_grep_no_warning(self):
|
|
207
|
+
al = _make_agent()
|
|
208
|
+
for i in range(9):
|
|
209
|
+
_add_tool_call(al, "grep", json.dumps({"pattern": f"pattern_{i}"}))
|
|
210
|
+
assert al._check_tool_call_repetition() is None
|
|
211
|
+
|
|
212
|
+
def test_interleaved_tools_dont_trigger(self):
|
|
213
|
+
al = _make_agent()
|
|
214
|
+
for i in range(20):
|
|
215
|
+
tool = "bash" if i % 2 == 0 else "grep"
|
|
216
|
+
_add_tool_call(al, tool, json.dumps({"arg": f"val_{i}"}))
|
|
217
|
+
# Not consecutive same tool — shouldn't trigger Check 2
|
|
218
|
+
# (might trigger Check 4 alternating though)
|
|
219
|
+
result = al._check_tool_call_repetition()
|
|
220
|
+
# Just verify it doesn't crash
|
|
221
|
+
assert result is None or "WARNING" in result
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# ============================================================================
|
|
225
|
+
# Check 3: Same File Read 5+ Times
|
|
226
|
+
# ============================================================================
|
|
227
|
+
|
|
228
|
+
class TestSameFileReadLoop:
|
|
229
|
+
"""Detects reading the same file 5+ times (different offsets)."""
|
|
230
|
+
|
|
231
|
+
def test_5_reads_same_file_triggers(self):
|
|
232
|
+
al = _make_agent()
|
|
233
|
+
for i in range(5):
|
|
234
|
+
_add_tool_call(al, "read_file", json.dumps({
|
|
235
|
+
"path": "src/auth.py", "offset": i * 50, "limit": 50
|
|
236
|
+
}))
|
|
237
|
+
result = al._check_tool_call_repetition()
|
|
238
|
+
assert result is not None
|
|
239
|
+
assert "WARNING" in result
|
|
240
|
+
|
|
241
|
+
def test_4_reads_same_file_no_trigger(self):
|
|
242
|
+
al = _make_agent()
|
|
243
|
+
for i in range(4):
|
|
244
|
+
_add_tool_call(al, "read_file", json.dumps({
|
|
245
|
+
"path": "src/auth.py", "offset": i * 50
|
|
246
|
+
}))
|
|
247
|
+
# 4 reads shouldn't trigger (threshold is 5)
|
|
248
|
+
# But might trigger same-tool-consecutive if we hit that threshold
|
|
249
|
+
result = al._check_tool_call_repetition()
|
|
250
|
+
# At 4 reads, same-tool limit for read_file is 10, so no trigger
|
|
251
|
+
assert result is None
|
|
252
|
+
|
|
253
|
+
def test_5_reads_different_files_no_trigger(self):
|
|
254
|
+
al = _make_agent()
|
|
255
|
+
for i in range(5):
|
|
256
|
+
_add_tool_call(al, "read_file", json.dumps({
|
|
257
|
+
"path": f"src/file_{i}.py"
|
|
258
|
+
}))
|
|
259
|
+
result = al._check_tool_call_repetition()
|
|
260
|
+
assert result is None
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# ============================================================================
|
|
264
|
+
# Check 4: Alternating Tool Pattern (A→B→A→B)
|
|
265
|
+
# ============================================================================
|
|
266
|
+
|
|
267
|
+
class TestAlternatingPattern:
|
|
268
|
+
"""Detects A→B→A→B→A→B→A→B alternating patterns."""
|
|
269
|
+
|
|
270
|
+
def test_8_alternating_triggers(self):
|
|
271
|
+
al = _make_agent()
|
|
272
|
+
for i in range(8):
|
|
273
|
+
tool = "grep" if i % 2 == 0 else "read_file"
|
|
274
|
+
_add_tool_call(al, tool, json.dumps({"arg": f"val_{i}"}))
|
|
275
|
+
result = al._check_tool_call_repetition()
|
|
276
|
+
assert result is not None
|
|
277
|
+
assert "WARNING" in result
|
|
278
|
+
|
|
279
|
+
def test_6_alternating_no_trigger(self):
|
|
280
|
+
al = _make_agent()
|
|
281
|
+
for i in range(6):
|
|
282
|
+
tool = "grep" if i % 2 == 0 else "read_file"
|
|
283
|
+
_add_tool_call(al, tool, json.dumps({"arg": f"val_{i}"}))
|
|
284
|
+
result = al._check_tool_call_repetition()
|
|
285
|
+
assert result is None
|
|
286
|
+
|
|
287
|
+
def test_3_tool_cycle_no_trigger(self):
|
|
288
|
+
"""A→B→C→A→B→C shouldn't trigger the 2-tool alternating check."""
|
|
289
|
+
al = _make_agent()
|
|
290
|
+
tools = ["grep", "read_file", "bash"]
|
|
291
|
+
for i in range(9):
|
|
292
|
+
_add_tool_call(al, tools[i % 3], json.dumps({"arg": f"val_{i}"}))
|
|
293
|
+
result = al._check_tool_call_repetition()
|
|
294
|
+
# 3-tool cycles are not detected by Check 4 (which only checks 2-tool)
|
|
295
|
+
# Might trigger same-tool-consecutive for individual tools though
|
|
296
|
+
assert result is None or "FORCE_STOP" not in result
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# ============================================================================
|
|
300
|
+
# Threshold Constants
|
|
301
|
+
# ============================================================================
|
|
302
|
+
|
|
303
|
+
class TestThresholdConstants:
|
|
304
|
+
"""Verify threshold constants are reasonable."""
|
|
305
|
+
|
|
306
|
+
def test_max_tool_turns_ceiling(self):
|
|
307
|
+
assert MAX_TOOL_TURNS <= 200
|
|
308
|
+
assert MAX_TOOL_TURNS >= 50
|
|
309
|
+
|
|
310
|
+
def test_warning_before_force_stop(self):
|
|
311
|
+
assert REPEAT_WARNING_THRESHOLD < REPEAT_FORCE_STOP_THRESHOLD
|
|
312
|
+
|
|
313
|
+
def test_warning_threshold_not_too_low(self):
|
|
314
|
+
assert REPEAT_WARNING_THRESHOLD >= 6
|
|
315
|
+
|
|
316
|
+
def test_force_stop_not_too_high(self):
|
|
317
|
+
assert REPEAT_FORCE_STOP_THRESHOLD <= 30
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
# ============================================================================
|
|
321
|
+
# Real-World Loop Scenarios
|
|
322
|
+
# ============================================================================
|
|
323
|
+
|
|
324
|
+
class TestRealWorldLoops:
|
|
325
|
+
"""Simulate actual user-reported looping scenarios."""
|
|
326
|
+
|
|
327
|
+
def test_ls_ltr_loop(self):
|
|
328
|
+
"""User reports: `ls -ltr` runs 5+ times."""
|
|
329
|
+
al = _make_agent()
|
|
330
|
+
tc = SimpleNamespace(tool_name="bash", raw_arguments='{"command":"ls -ltr"}')
|
|
331
|
+
|
|
332
|
+
# First 2 calls go through
|
|
333
|
+
al._circuit_breaker_record(tc, "total 48\n-rw-r--r-- 1 user user 1234 auth.py")
|
|
334
|
+
al._circuit_breaker_record(tc, "total 48\n-rw-r--r-- 1 user user 1234 auth.py")
|
|
335
|
+
|
|
336
|
+
# 3rd call is blocked
|
|
337
|
+
assert al._circuit_breaker_check(tc) is not None
|
|
338
|
+
|
|
339
|
+
def test_grep_same_pattern_loop(self):
|
|
340
|
+
"""Model greps for the same pattern 10 times."""
|
|
341
|
+
al = _make_agent()
|
|
342
|
+
for _ in range(REPEAT_WARNING_THRESHOLD):
|
|
343
|
+
_add_tool_call(al, "grep", '{"pattern":"def authenticate","path":"."}')
|
|
344
|
+
assert al._check_tool_call_repetition() is not None
|
|
345
|
+
|
|
346
|
+
def test_read_file_offset_evasion(self):
|
|
347
|
+
"""Model reads same file with incrementing offset to evade detection."""
|
|
348
|
+
al = _make_agent()
|
|
349
|
+
for i in range(5):
|
|
350
|
+
_add_tool_call(al, "read_file", json.dumps({
|
|
351
|
+
"path": "django/db/models/query.py",
|
|
352
|
+
"offset": i * 100,
|
|
353
|
+
"limit": 100,
|
|
354
|
+
}))
|
|
355
|
+
assert al._check_tool_call_repetition() is not None
|
|
356
|
+
|
|
357
|
+
def test_bash_exploration_loop(self):
|
|
358
|
+
"""Model runs 8+ different bash commands exploring the filesystem."""
|
|
359
|
+
al = _make_agent()
|
|
360
|
+
commands = [
|
|
361
|
+
"find . -name '*.py'", "ls src/", "cat README.md",
|
|
362
|
+
"wc -l src/*.py", "head -20 setup.py", "ls -la tests/",
|
|
363
|
+
"find . -type f | wc -l", "du -sh src/",
|
|
364
|
+
]
|
|
365
|
+
for cmd in commands:
|
|
366
|
+
_add_tool_call(al, "bash", json.dumps({"command": cmd}))
|
|
367
|
+
result = al._check_tool_call_repetition()
|
|
368
|
+
assert result is not None # 8 consecutive bash calls
|
|
369
|
+
|
|
370
|
+
def test_grep_read_alternation(self):
|
|
371
|
+
"""Model alternates between grep and read_file without making progress."""
|
|
372
|
+
al = _make_agent()
|
|
373
|
+
for i in range(8):
|
|
374
|
+
if i % 2 == 0:
|
|
375
|
+
_add_tool_call(al, "grep", json.dumps({"pattern": f"func_{i//2}"}))
|
|
376
|
+
else:
|
|
377
|
+
_add_tool_call(al, "read_file", json.dumps({"path": f"file_{i//2}.py"}))
|
|
378
|
+
assert al._check_tool_call_repetition() is not None
|
|
379
|
+
|
|
380
|
+
def test_normal_workflow_no_false_positive(self):
|
|
381
|
+
"""A normal workflow: grep → read → edit → verify should NOT trigger."""
|
|
382
|
+
al = _make_agent()
|
|
383
|
+
_add_tool_call(al, "grep", '{"pattern":"def login"}')
|
|
384
|
+
_add_tool_call(al, "read_file", '{"path":"auth.py","offset":10,"limit":20}')
|
|
385
|
+
_add_tool_call(al, "search_replace", '{"file_path":"auth.py","content":"..."}')
|
|
386
|
+
_add_tool_call(al, "read_file", '{"path":"auth.py","offset":10,"limit":20}')
|
|
387
|
+
assert al._check_tool_call_repetition() is None
|
|
388
|
+
|
|
389
|
+
def test_investigate_then_fix_no_false_positive(self):
|
|
390
|
+
"""3 greps + 2 reads + 1 edit = normal bug fix workflow."""
|
|
391
|
+
al = _make_agent()
|
|
392
|
+
_add_tool_call(al, "grep", '{"pattern":"class User"}')
|
|
393
|
+
_add_tool_call(al, "grep", '{"pattern":"def save"}')
|
|
394
|
+
_add_tool_call(al, "read_file", '{"path":"models.py"}')
|
|
395
|
+
_add_tool_call(al, "grep", '{"pattern":"validate"}')
|
|
396
|
+
_add_tool_call(al, "read_file", '{"path":"validators.py"}')
|
|
397
|
+
_add_tool_call(al, "search_replace", '{"file_path":"models.py","content":"..."}')
|
|
398
|
+
assert al._check_tool_call_repetition() is None
|