drydock-cli 3.1.29__tar.gz → 3.1.31__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-3.1.29/drydock_cli.egg-info → drydock_cli-3.1.31}/PKG-INFO +1 -1
- drydock_cli-3.1.31/drydock/asr.py +227 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/cli.py +9 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/config.py +6 -0
- drydock_cli-3.1.31/drydock/mission.py +471 -0
- drydock_cli-3.1.31/drydock/mission_cli.py +362 -0
- drydock_cli-3.1.31/drydock/mission_run.py +593 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/ratchet.py +16 -11
- {drydock_cli-3.1.29 → drydock_cli-3.1.31/drydock_cli.egg-info}/PKG-INFO +1 -1
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock_cli.egg-info/SOURCES.txt +9 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/pyproject.toml +1 -1
- drydock_cli-3.1.31/tests/test_asr.py +140 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_capacity.py +2 -2
- drydock_cli-3.1.31/tests/test_main_api_key.py +76 -0
- drydock_cli-3.1.31/tests/test_mission.py +136 -0
- drydock_cli-3.1.31/tests/test_mission_cli.py +155 -0
- drydock_cli-3.1.31/tests/test_mission_run.py +465 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_ratchet.py +13 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/LICENSE +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/NOTICE +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/README.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/__init__.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/__main__.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/advisor.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/agent.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/bottleneck.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/budget.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/__init__.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/document-canvas.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/fiar-assess.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/fiar-cap.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/fiar-evidence.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/fiar-readiness.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/ml-data.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/ml-debug.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/ml-finetune.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/ml-metrics.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/ml-rl.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/ml-train.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/nist-ai-rmf.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/nist-csf.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/rmf-categorize.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/rmf-control.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/rmf-poam.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/rmf-review.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/stig-assess.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/builtin_skills/stig-remediate.md +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/capacity.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/cci.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/comms/__init__.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/comms/attention.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/comms/channels.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/comms/events.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/compaction.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/detect.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/doccanvas.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/eratchet.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/events.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/extract.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/fiar.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/gittools.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/graphrag.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/groundtruth.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/guards.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/jobs.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/mcp.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/pdfredbox.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/phases.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/poam.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/predictions.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/progress.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/providers.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/recipes.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/recovery.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/resume.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/rmf.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/rmf_graph.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/skills.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/stig.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/subagents.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/suggest.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/swarm.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/task_state.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/tool_policy.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/tool_result.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/tool_select.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/tool_validate.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/tools/__init__.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/trajectory.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/tui/app.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/tuning.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/verification.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock/web.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/setup.cfg +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_advisor.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_approval.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_back_command.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bash_background.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bash_binary_output.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bash_crossplatform.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bash_sanitize.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bash_shell.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bash_stdin.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bash_stop_partial.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bash_timeout_param.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_bottleneck.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_budget.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_cci.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_comms.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_compaction.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_config.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_context_limit_issue25.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_criteria_coverage.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_cycling_detection.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_declared_deps.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_degenerate_argument.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_detect.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_doccanvas.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_e2e_connected.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_edit_replace_all.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_edit_thrash_and_compaction.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_effort_governor.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_eratchet.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_events.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_extract.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_fiar.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_gittools.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_glob_edit_edges.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_graphify_example.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_graphrag_quoted_path.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_graphrag_sqlite.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_grep_and_read_robust.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_groundtruth_predictions.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_jobs.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_knowledge_tool_available.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_mcp.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_model_registry.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_overthink_interrupt.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_pdfredbox.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_phases.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_poam.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_progress.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_ratchet_offer.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_read_index.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_recipes.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_recovery.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_recovery_config.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_recovery_integration.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_repeated_outcome.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_repetition_interrupt.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_resume_events.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_resume_restore.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_resume_snapshot.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_rmf.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_rmf_graph.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_rmf_stig_graph.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_rolling_plan.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_screenshot.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_server_probe.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_skills.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_sqlite_events.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_stall_retry.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_stig.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_stop.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_subagent.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_subagents.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_suggest.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_swarm.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_system_prompt_help.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_task_state.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_timeline.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_todo.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_tool_arg_coercion.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_tool_arg_coercion_more.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_tool_canonical.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_tool_policy.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_tool_result.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_tool_select.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_tool_validate.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_trajectory.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_tui.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_tuning.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_verification_gate.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_viewimage.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_web_tools.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_windows_shell.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_worker_subagent.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_write_content_coerce.py +0 -0
- {drydock_cli-3.1.29 → drydock_cli-3.1.31}/tests/test_xccdf.py +0 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Adaptive Swarm Runtime (ASR) — the search-decision core.
|
|
2
|
+
|
|
3
|
+
This is the resource-agnostic brain of the adaptive swarm (PRD "Resource-Aware Adaptive
|
|
4
|
+
Swarms"): it turns a compute/time BUDGET into decisions about *where the next unit of inference
|
|
5
|
+
should go* and *when to stop*. It deliberately holds no model, GPU, or scheduler state — those
|
|
6
|
+
live in capacity.py (concurrency), swarm.py (blackboard/candidates/worktrees) and the mission
|
|
7
|
+
store (durable state). Keeping the decisions pure makes them unit-testable and reusable.
|
|
8
|
+
|
|
9
|
+
Three pieces, in dependency order:
|
|
10
|
+
* Budget — a finite termination envelope parsed from a user string (§15).
|
|
11
|
+
* Convergence — should the swarm keep going? Stops on success / budget / no-improvement /
|
|
12
|
+
when marginal value Δquality/Δcompute falls below a floor (§23).
|
|
13
|
+
* AdaptiveAllocator — split the next compute pool across live branches by expected value,
|
|
14
|
+
pruning the weak ones (§17). Bad ideas must not get the same compute as good.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
|
|
21
|
+
# ── Budget (§15) ────────────────────────────────────────────────────────────
|
|
22
|
+
_DUR = re.compile(r"^\s*([\d.]+)\s*([hms])\s*$", re.I)
|
|
23
|
+
_TOK = re.compile(r"^\s*([\d.]+)\s*([kmg]?)\s*[-_]?tokens?\s*$", re.I)
|
|
24
|
+
_MULT = {"": 1, "k": 1_000, "m": 1_000_000, "g": 1_000_000_000}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class Budget:
|
|
29
|
+
"""A finite stopping envelope (§10/§15). Every field is optional, but a Budget with no
|
|
30
|
+
finite limit is a bug — `is_finite` guards against "run forever"."""
|
|
31
|
+
wall_secs: float | None = None
|
|
32
|
+
tokens: int | None = None
|
|
33
|
+
generations: int | None = None
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def is_finite(self) -> bool:
|
|
37
|
+
return any(v is not None for v in (self.wall_secs, self.tokens, self.generations))
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def parse(cls, spec: str) -> "Budget":
|
|
41
|
+
"""Parse one user token: '2h' / '30m' / '90s' (wall) or '100M-tokens' / '50k-tokens'
|
|
42
|
+
(compute). Unknown strings raise ValueError so a typo never silently means 'unbounded'."""
|
|
43
|
+
s = (spec or "").strip()
|
|
44
|
+
md = _DUR.match(s)
|
|
45
|
+
if md:
|
|
46
|
+
n, unit = float(md.group(1)), md.group(2).lower()
|
|
47
|
+
return cls(wall_secs=n * {"h": 3600, "m": 60, "s": 1}[unit])
|
|
48
|
+
mt = _TOK.match(s)
|
|
49
|
+
if mt:
|
|
50
|
+
return cls(tokens=int(float(mt.group(1)) * _MULT[mt.group(2).lower()]))
|
|
51
|
+
raise ValueError(f"unparseable budget: {spec!r} (try '2h', '30m', '100M-tokens')")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ── Convergence (§23) ─────────────────────────────────────────────────────────
|
|
55
|
+
@dataclass
|
|
56
|
+
class GenerationRecord:
|
|
57
|
+
gen: int
|
|
58
|
+
best_score: float # best branch quality so far (higher = better)
|
|
59
|
+
cumulative_tokens: int # total inference tokens spent through this generation
|
|
60
|
+
elapsed_s: float # wall-clock since start
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class Convergence:
|
|
65
|
+
"""Decides whether the swarm should run another generation (§23). "More agents are not
|
|
66
|
+
automatically better": we stop the moment any finite condition trips, INCLUDING the marginal
|
|
67
|
+
one — when Δquality per unit compute drops below `min_marginal`, extra inference is waste."""
|
|
68
|
+
budget: Budget
|
|
69
|
+
success_score: float = 100.0 # stop when best branch reaches this (e.g. all tests pass)
|
|
70
|
+
no_improve_gens: int = 3 # stop after this many generations with no best-score gain
|
|
71
|
+
min_marginal: float = 0.0 # floor on Δscore per 1k tokens; <=0 disables the check
|
|
72
|
+
eps: float = 1e-9
|
|
73
|
+
history: list[GenerationRecord] = field(default_factory=list)
|
|
74
|
+
|
|
75
|
+
def record(self, best_score: float, cumulative_tokens: int, elapsed_s: float) -> None:
|
|
76
|
+
self.history.append(GenerationRecord(len(self.history) + 1, best_score,
|
|
77
|
+
cumulative_tokens, elapsed_s))
|
|
78
|
+
|
|
79
|
+
def _gens_since_improvement(self) -> int:
|
|
80
|
+
"""Generations since the best score was FIRST reached (a plateau at the best counts)."""
|
|
81
|
+
if not self.history:
|
|
82
|
+
return 0
|
|
83
|
+
best = max(r.best_score for r in self.history)
|
|
84
|
+
first_best = next(i for i, r in enumerate(self.history) if r.best_score >= best - self.eps)
|
|
85
|
+
return (len(self.history) - 1) - first_best
|
|
86
|
+
|
|
87
|
+
def marginal_value(self) -> float | None:
|
|
88
|
+
"""Δscore per 1,000 tokens over the last generation, or None with <2 records."""
|
|
89
|
+
if len(self.history) < 2:
|
|
90
|
+
return None
|
|
91
|
+
a, b = self.history[-2], self.history[-1]
|
|
92
|
+
dtok = b.cumulative_tokens - a.cumulative_tokens
|
|
93
|
+
if dtok <= 0:
|
|
94
|
+
return None
|
|
95
|
+
return (b.best_score - a.best_score) / (dtok / 1000.0)
|
|
96
|
+
|
|
97
|
+
def should_continue(self) -> tuple[bool, str]:
|
|
98
|
+
"""(keep_going?, reason). Checked in priority order; the reason names the stop trigger."""
|
|
99
|
+
last = self.history[-1] if self.history else None
|
|
100
|
+
if last is not None and last.best_score >= self.success_score - self.eps:
|
|
101
|
+
return False, "success score reached"
|
|
102
|
+
b = self.budget
|
|
103
|
+
if b.wall_secs is not None and last is not None and last.elapsed_s >= b.wall_secs:
|
|
104
|
+
return False, "wall-clock budget exhausted"
|
|
105
|
+
if b.tokens is not None and last is not None and last.cumulative_tokens >= b.tokens:
|
|
106
|
+
return False, "token budget exhausted"
|
|
107
|
+
if b.generations is not None and len(self.history) >= b.generations:
|
|
108
|
+
return False, "generation budget reached"
|
|
109
|
+
if self.no_improve_gens > 0 and self._gens_since_improvement() >= self.no_improve_gens:
|
|
110
|
+
return False, f"no improvement in {self.no_improve_gens} generations"
|
|
111
|
+
mv = self.marginal_value()
|
|
112
|
+
if self.min_marginal > 0 and mv is not None and mv < self.min_marginal:
|
|
113
|
+
return False, f"marginal value {mv:.3f} < floor {self.min_marginal:.3f} (Δquality/Δcompute)"
|
|
114
|
+
return True, "continue"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ── Adaptive compute allocation (§17) ──────────────────────────────────────────
|
|
118
|
+
@dataclass
|
|
119
|
+
class Allocation:
|
|
120
|
+
grants: dict[str, int] # branch id -> tokens granted this round
|
|
121
|
+
terminated: list[str] # branch ids pruned (0 tokens, killed)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def allocate(branches: list[tuple[str, float]], pool_tokens: int, *,
|
|
125
|
+
prune_below: float = 0.0, keep_top: int | None = None,
|
|
126
|
+
bias: float = 1.5, min_grant: int = 0) -> Allocation:
|
|
127
|
+
"""Split `pool_tokens` across branches by expected value (§17).
|
|
128
|
+
|
|
129
|
+
branches: (id, score) with higher score = more promising. A branch is TERMINATED (gets 0)
|
|
130
|
+
if its score is below `prune_below` or it falls outside the top `keep_top`. Survivors share
|
|
131
|
+
the pool in proportion to score**bias, so leaders get disproportionately more (bias>1) — the
|
|
132
|
+
system must not spend equally on good and bad ideas. `min_grant` guarantees a floor so a
|
|
133
|
+
kept-but-trailing branch still gets a probe. Deterministic; ties broken by input order."""
|
|
134
|
+
if pool_tokens <= 0 or not branches:
|
|
135
|
+
return Allocation({}, [b for b, _ in branches])
|
|
136
|
+
ranked = sorted(branches, key=lambda x: x[1], reverse=True)
|
|
137
|
+
survivors = [(b, s) for b, s in ranked if s >= prune_below]
|
|
138
|
+
if keep_top is not None:
|
|
139
|
+
survivors = survivors[:max(0, keep_top)]
|
|
140
|
+
kept_ids = {b for b, _ in survivors}
|
|
141
|
+
terminated = [b for b, _ in branches if b not in kept_ids]
|
|
142
|
+
if not survivors:
|
|
143
|
+
return Allocation({}, terminated)
|
|
144
|
+
weights = [(b, max(s, 0.0) ** bias) for b, s in survivors]
|
|
145
|
+
total_w = sum(w for _, w in weights) or float(len(weights))
|
|
146
|
+
grants: dict[str, int] = {}
|
|
147
|
+
for b, w in weights:
|
|
148
|
+
share = (w / total_w) if total_w else 1.0 / len(weights)
|
|
149
|
+
grants[b] = max(min_grant, int(round(pool_tokens * share)))
|
|
150
|
+
# correct rounding drift so grants sum to the pool (adjust the top branch)
|
|
151
|
+
drift = pool_tokens - sum(grants.values())
|
|
152
|
+
if drift and weights:
|
|
153
|
+
top = weights[0][0]
|
|
154
|
+
grants[top] = max(min_grant, grants[top] + drift)
|
|
155
|
+
return Allocation(grants, terminated)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ── Planner: budget + intensity + hardware → population plan (§15/§16, §5/§9) ──
|
|
159
|
+
# Diverse roles for solution-space coverage (§9/§10). Correlated agents waste compute.
|
|
160
|
+
ROLES = [
|
|
161
|
+
"first-principles solver", "conventional solver", "skeptic", "debugger",
|
|
162
|
+
"security reviewer", "researcher", "minimalist", "adversarial critic",
|
|
163
|
+
"performance specialist", "alternative-architecture agent",
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
# Per-intensity search shape (§16): initial population, critic count, generation cap, and how
|
|
167
|
+
# many branches survive each prune. Population is a SEARCH choice — independent of hardware.
|
|
168
|
+
INTENSITY = {
|
|
169
|
+
"light": {"population": 3, "critics": 1, "generations": 2, "keep": 2},
|
|
170
|
+
"standard": {"population": 8, "critics": 2, "generations": 4, "keep": 4},
|
|
171
|
+
"deep": {"population": 16, "critics": 3, "generations": 6, "keep": 6},
|
|
172
|
+
"extreme": {"population": 24, "critics": 5, "generations": 10, "keep": 8},
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass
|
|
177
|
+
class SwarmPlan:
|
|
178
|
+
intensity: str
|
|
179
|
+
initial_population: int # logical agents in the explore stage (§9)
|
|
180
|
+
max_concurrency: int # simultaneous inference — HARDWARE bound (§5), != population
|
|
181
|
+
critics: int # judge/critic agents (§11)
|
|
182
|
+
generations: int # generation cap (a convergence bound, §23)
|
|
183
|
+
keep: int # survivors promoted past each prune (§12)
|
|
184
|
+
roles: list[str] # diversified assignments (§10)
|
|
185
|
+
estimated_logical: int # rough total agents incl. spawned descendants (§13) — info only
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def queued_at_start(self) -> int:
|
|
189
|
+
"""Logical agents that must wait because concurrency < population (§18)."""
|
|
190
|
+
return max(0, self.initial_population - self.max_concurrency)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def choose_intensity(budget: Budget) -> str:
|
|
194
|
+
"""Map a budget to a search intensity for `--swarm auto` (§16). Bigger budget → deeper
|
|
195
|
+
search. Falls back to 'standard' when the budget carries no size signal."""
|
|
196
|
+
if budget.wall_secs is not None:
|
|
197
|
+
s = budget.wall_secs
|
|
198
|
+
return "light" if s < 900 else "standard" if s < 3600 else "deep" if s < 14400 else "extreme"
|
|
199
|
+
if budget.tokens is not None:
|
|
200
|
+
t = budget.tokens
|
|
201
|
+
return ("light" if t < 1_000_000 else "standard" if t < 10_000_000
|
|
202
|
+
else "deep" if t < 50_000_000 else "extreme")
|
|
203
|
+
if budget.generations is not None:
|
|
204
|
+
g = budget.generations
|
|
205
|
+
return "light" if g <= 2 else "standard" if g <= 4 else "deep" if g <= 6 else "extreme"
|
|
206
|
+
return "standard"
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def plan(budget: Budget, concurrency: int, *, intensity: str = "auto") -> SwarmPlan:
|
|
210
|
+
"""Turn a budget + measured hardware concurrency into a population plan (§15/§16).
|
|
211
|
+
|
|
212
|
+
KEY: logical population comes from the intensity (a search decision); `concurrency` only
|
|
213
|
+
caps how many run at once (§5). On a 1-GPU box a 16-agent 'deep' plan still has 16 logical
|
|
214
|
+
agents — the rest queue (§18); the penalty is wall-clock, not lost search capability. This
|
|
215
|
+
is the opposite of capacity.swarm_size(), which caps logical count AT concurrency."""
|
|
216
|
+
intensity = choose_intensity(budget) if intensity == "auto" else intensity
|
|
217
|
+
if intensity not in INTENSITY:
|
|
218
|
+
raise ValueError(f"unknown intensity {intensity!r} (light|standard|deep|extreme|auto)")
|
|
219
|
+
p = INTENSITY[intensity]
|
|
220
|
+
pop = p["population"]
|
|
221
|
+
roles = [ROLES[i % len(ROLES)] for i in range(pop)]
|
|
222
|
+
# estimate total logical agents: initial + ~one descendant per survivor per later generation
|
|
223
|
+
estimated = pop + p["keep"] * max(0, p["generations"] - 1)
|
|
224
|
+
return SwarmPlan(intensity=intensity, initial_population=pop,
|
|
225
|
+
max_concurrency=max(1, concurrency), critics=p["critics"],
|
|
226
|
+
generations=p["generations"], keep=p["keep"], roles=roles,
|
|
227
|
+
estimated_logical=estimated)
|
|
@@ -383,6 +383,15 @@ def main():
|
|
|
383
383
|
cfg["cwd"] = os.getcwd()
|
|
384
384
|
sys.exit(swarm_run_cli(sys.argv[2:], config=cfg))
|
|
385
385
|
|
|
386
|
+
# `drydock mission <create|status|run|resume|…>` — long-horizon autonomous missions.
|
|
387
|
+
if len(sys.argv) > 1 and sys.argv[1] == "mission":
|
|
388
|
+
from drydock import config as cfgmod
|
|
389
|
+
from drydock.mission_cli import run_cli as mission_run_cli
|
|
390
|
+
cfg = cfgmod.resolve({}, cfgmod.default_config_path())
|
|
391
|
+
cfgmod.resolve_active_model(cfg)
|
|
392
|
+
cfg["cwd"] = os.getcwd()
|
|
393
|
+
sys.exit(mission_run_cli(sys.argv[2:], config=cfg))
|
|
394
|
+
|
|
386
395
|
parser = argparse.ArgumentParser(description="DryDock — local coding agent")
|
|
387
396
|
parser.add_argument(
|
|
388
397
|
"--version", "-V", action="version", version=f"drydock {__version__}"
|
|
@@ -27,6 +27,12 @@ DEFAULTS: dict[str, object] = {
|
|
|
27
27
|
# default invisibly, which left base_url absent from the file. For a non-vllm
|
|
28
28
|
# provider, override base_url (or use --base-url / the first-run prompt).
|
|
29
29
|
"base_url": "http://localhost:8000/v1",
|
|
30
|
+
# API key for the MAIN model endpoint, sent as `Authorization: Bearer <key>`.
|
|
31
|
+
# Leave "" for a local server that needs no auth (llama.cpp/Ollama/LM Studio);
|
|
32
|
+
# set it for a hosted OpenAI-compatible endpoint (e.g. a vLLM behind a gateway,
|
|
33
|
+
# OpenRouter, Together). Mirrors `advisor_api_key`. Can also come from the
|
|
34
|
+
# provider's api_key_env (e.g. OPENAI_API_KEY); the config value wins.
|
|
35
|
+
"api_key": "",
|
|
30
36
|
"max_tokens": 8192, # 4096 truncated large file writes mid-JSON (→ _raw fail)
|
|
31
37
|
"temperature": 0.2,
|
|
32
38
|
# The model server's context window (llama.cpp -c / vLLM --max-model-len).
|