drydock-cli 3.1.5__tar.gz → 3.1.7__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.5/drydock_cli.egg-info → drydock_cli-3.1.7}/PKG-INFO +70 -1
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/README.md +69 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/fiar.py +148 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/providers.py +11 -0
- drydock_cli-3.1.7/drydock/ratchet.py +522 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tools/__init__.py +56 -1
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tui/app.py +135 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7/drydock_cli.egg-info}/PKG-INFO +70 -1
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock_cli.egg-info/SOURCES.txt +2 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/pyproject.toml +1 -1
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_doccanvas.py +45 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_fiar.py +57 -0
- drydock_cli-3.1.7/tests/test_ratchet.py +324 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/LICENSE +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/NOTICE +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/__init__.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/__main__.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/advisor.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/agent.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/budget.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/__init__.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/document-canvas.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/fiar-assess.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/fiar-cap.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/fiar-evidence.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/fiar-readiness.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/ml-data.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/ml-debug.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/ml-finetune.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/ml-metrics.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/ml-rl.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/ml-train.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/nist-ai-rmf.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/nist-csf.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/rmf-categorize.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/rmf-control.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/rmf-poam.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/rmf-review.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/stig-assess.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/stig-remediate.md +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/cci.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/cli.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/compaction.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/config.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/detect.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/doccanvas.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/events.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/extract.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/gittools.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/graphrag.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/guards.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/mcp.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/phases.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/poam.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/progress.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/recipes.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/recovery.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/resume.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/rmf.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/rmf_graph.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/skills.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/stig.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/subagents.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/suggest.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/task_state.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tool_policy.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tool_result.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tool_select.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tool_validate.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/trajectory.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tuning.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/verification.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/web.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/setup.cfg +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_advisor.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_approval.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_back_command.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_background.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_binary_output.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_crossplatform.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_sanitize.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_shell.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_stdin.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_stop_partial.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_timeout_param.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_budget.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_cci.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_compaction.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_config.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_context_limit_issue25.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_criteria_coverage.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_cycling_detection.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_degenerate_argument.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_detect.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_e2e_connected.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_edit_replace_all.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_effort_governor.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_events.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_extract.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_gittools.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_glob_edit_edges.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_graphify_example.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_graphrag_quoted_path.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_graphrag_sqlite.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_grep_and_read_robust.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_mcp.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_model_registry.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_overthink_interrupt.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_phases.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_poam.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_progress.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_read_index.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_recipes.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_recovery.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_recovery_config.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_recovery_integration.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_repeated_outcome.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_repetition_interrupt.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_resume_events.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_resume_restore.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_resume_snapshot.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_rmf.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_rmf_graph.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_rmf_stig_graph.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_rolling_plan.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_screenshot.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_server_probe.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_skills.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_sqlite_events.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_stall_retry.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_stig.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_stop.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_subagent.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_subagents.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_suggest.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_system_prompt_help.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_task_state.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_timeline.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_todo.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_arg_coercion.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_arg_coercion_more.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_canonical.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_policy.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_result.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_select.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_validate.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_trajectory.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tui.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tuning.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_verification_gate.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_viewimage.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_web_tools.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_windows_shell.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_worker_subagent.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_write_content_coerce.py +0 -0
- {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_xccdf.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drydock-cli
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.7
|
|
4
4
|
Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
|
|
5
5
|
Author: Frank Bobe III
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -112,6 +112,10 @@ A full agentic CLI harness — every tool below is clean-room and dependency-fre
|
|
|
112
112
|
- **Cross-platform** — runs natively on Linux, macOS, and **Windows via PowerShell/cmd**
|
|
113
113
|
(no WSL or Git-Bash required); `/shell` shows which shell your commands run in.
|
|
114
114
|
- **Loops** — `/loop <count> <prompt>` runs a prompt iteratively (Esc stops).
|
|
115
|
+
- **Ratchet** — `/ratchet <goal>` keeps solving across rounds, snapshotting the
|
|
116
|
+
workspace whenever more tests pass and rolling back regressions, until it goes
|
|
117
|
+
green. The verifier auto-detects (pytest/cargo/go/npm/make); `--verify "<cmd>"`
|
|
118
|
+
overrides. Progress can't slip backward.
|
|
115
119
|
|
|
116
120
|
## Slash commands
|
|
117
121
|
|
|
@@ -129,6 +133,7 @@ Typed into the prompt. The agent also knows these, so you can just **ask it**
|
|
|
129
133
|
| `/skills new <name> <prompt>` | Create a reusable `/<name>` skill (use `$ARGS` for input) |
|
|
130
134
|
| `/<name>` | Run a skill |
|
|
131
135
|
| `/loop <count> <prompt>` | Repeat a prompt N times (Esc stops) |
|
|
136
|
+
| `/ratchet <goal>` | Solve across rounds, snapshotting on verifier gains, rolling back regressions. Verifier auto-detects; override with `--verify "<cmd>"` (`--rounds N`, `--fitness auto\|exitcode\|<regex>`) |
|
|
132
137
|
| `/mcp` | List connected MCP servers + their tools |
|
|
133
138
|
| `/rmf bootstrap [families]` | Ingest the NIST SP 800-53 catalog (RMF automation) |
|
|
134
139
|
| `/rmf-control` · `/rmf-categorize` · `/rmf-review` · `/rmf-poam` | Bundled RMF skills |
|
|
@@ -267,6 +272,22 @@ exports the open findings to a deterministic **eMASS POA&M CSV** — Control (fr
|
|
|
267
272
|
the CCI map), Vulnerability Description, `POA&M Status=Ongoing`, Milestone (the
|
|
268
273
|
Fix Text), and Severity (CAT I/II/III → High/Moderate/Low) — no LLM, stdlib only.
|
|
269
274
|
|
|
275
|
+
### FIAR audit-readiness (DoD financial-statement audit)
|
|
276
|
+
|
|
277
|
+
Model a Financial Improvement and Audit Readiness engagement — a seeded key-control
|
|
278
|
+
matrix per business cycle (FBWT, P2P, PP&E, INV, CIVPAY, REIM, FR, ITGC), the five FS
|
|
279
|
+
assertions, a deterministic **evidence-chain validator** (a control can't be called
|
|
280
|
+
effective on an incomplete population→sample→…→GL→assertion trace), findings (NFRs), and
|
|
281
|
+
CAPs. `python -m drydock.fiar new|controls|control|assess|reconcile|package`; skills
|
|
282
|
+
`/fiar-assess /fiar-evidence /fiar-readiness /fiar-cap`.
|
|
283
|
+
|
|
284
|
+
**KSD evidence packaging.** `fiar package <engagement> <out> --evidence-dir <dir>` assembles
|
|
285
|
+
an audit binder — `index.md` + `index.json` mapping every control → assertions → KSDs →
|
|
286
|
+
status → evidence-chain completeness → findings — and **collects the actual evidence files
|
|
287
|
+
each test cited**, zipped. Add `--redact "<term>"` (repeatable) to **red-box** names/secrets
|
|
288
|
+
in the evidence *and* the manifest for a releasable version (verified via the Document
|
|
289
|
+
Canvas); the original engagement is never touched. Also exposed as the `FiarPackage` tool.
|
|
290
|
+
|
|
270
291
|
### Document Canvas — editing documents far larger than the context window
|
|
271
292
|
|
|
272
293
|
Edit a 300-, 800-, 1000-page document the way you edit a large codebase: **search,
|
|
@@ -351,6 +372,54 @@ docker run -it --add-host=host.docker.internal:host-gateway \
|
|
|
351
372
|
`-v "$PWD:/work"` mounts your project so the agent can read/edit it. Tags:
|
|
352
373
|
`fbobe3/drydock:latest` and `fbobe3/drydock:<version>` (e.g. `:3.0.135`).
|
|
353
374
|
|
|
375
|
+
### Serving Gemma-4-31B
|
|
376
|
+
|
|
377
|
+
Drydock is provider-agnostic, but its primary target is dense **Gemma-4-31B**.
|
|
378
|
+
Two proven ways to serve it on a 2×GPU box, both exposing an OpenAI-compatible
|
|
379
|
+
API on `:8000` as model `gemma4`:
|
|
380
|
+
|
|
381
|
+
**llama.cpp** — QAT GGUF, flexible concurrency (more slots if you need them):
|
|
382
|
+
|
|
383
|
+
```
|
|
384
|
+
llama-server -m gemma-4-31B-it-qat-UD-Q4_K_XL.gguf \
|
|
385
|
+
-c 65536 -np 2 --host 0.0.0.0 --port 8000 --alias gemma4
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**vLLM** — w4a16 QAT, ~2× faster per request, 128K context:
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
docker run -d --name vllm-prod --gpus all --ipc=host --restart unless-stopped \
|
|
392
|
+
-v /data3/Models:/models -p 8000:8000 \
|
|
393
|
+
-e NCCL_P2P_DISABLE=1 \
|
|
394
|
+
vllm/vllm-openai:v0.26.0 \
|
|
395
|
+
--model /models/gemma-4-31B-it-qat-w4a16-ct \
|
|
396
|
+
--served-model-name gemma4 \
|
|
397
|
+
--tensor-parallel-size 2 \
|
|
398
|
+
--max-model-len 131072 \
|
|
399
|
+
--max-num-seqs 2 \
|
|
400
|
+
--gpu-memory-utilization 0.97 \
|
|
401
|
+
--kv-cache-dtype fp8 \
|
|
402
|
+
--tool-call-parser gemma4 \
|
|
403
|
+
--enable-auto-tool-choice
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Point drydock at either with
|
|
407
|
+
`--provider vllm --base-url http://<host>:8000/v1 --model gemma4`.
|
|
408
|
+
|
|
409
|
+
**vLLM-on-Gemma-4 notes** (drydock handles these for you as of **3.1.7**):
|
|
410
|
+
|
|
411
|
+
- **`skip_special_tokens: false`** is sent on every request — without it, a turn
|
|
412
|
+
truncated at `max_tokens` (tool-call / reasoning tails) comes back with *empty*
|
|
413
|
+
content, which looks like a refusal. If `finish_reason == "length"`, retry with
|
|
414
|
+
a larger `max_tokens` (≥ 2000 for reasoning-heavy calls).
|
|
415
|
+
- **Reasoning is off by default.** Enable per-request via
|
|
416
|
+
`chat_template_kwargs.enable_thinking`; the vLLM reasoning parser is disabled,
|
|
417
|
+
so `<|channel>thought …` markers arrive inside `content` (stripped client-side).
|
|
418
|
+
- **`--max-num-seqs 2`** caps concurrency at 2 — the cost of fitting 128K context
|
|
419
|
+
into the VRAM. Use llama.cpp if you need more concurrent sessions than context.
|
|
420
|
+
- Tool-calling is the standard OpenAI format; `/props` is llama.cpp-only (vLLM
|
|
421
|
+
404s it — drydock falls back to `/v1/models` for the context length).
|
|
422
|
+
|
|
354
423
|
## Using it
|
|
355
424
|
|
|
356
425
|
Type a task and press **Enter**. Drydock reads/writes/edits files and runs
|
|
@@ -88,6 +88,10 @@ A full agentic CLI harness — every tool below is clean-room and dependency-fre
|
|
|
88
88
|
- **Cross-platform** — runs natively on Linux, macOS, and **Windows via PowerShell/cmd**
|
|
89
89
|
(no WSL or Git-Bash required); `/shell` shows which shell your commands run in.
|
|
90
90
|
- **Loops** — `/loop <count> <prompt>` runs a prompt iteratively (Esc stops).
|
|
91
|
+
- **Ratchet** — `/ratchet <goal>` keeps solving across rounds, snapshotting the
|
|
92
|
+
workspace whenever more tests pass and rolling back regressions, until it goes
|
|
93
|
+
green. The verifier auto-detects (pytest/cargo/go/npm/make); `--verify "<cmd>"`
|
|
94
|
+
overrides. Progress can't slip backward.
|
|
91
95
|
|
|
92
96
|
## Slash commands
|
|
93
97
|
|
|
@@ -105,6 +109,7 @@ Typed into the prompt. The agent also knows these, so you can just **ask it**
|
|
|
105
109
|
| `/skills new <name> <prompt>` | Create a reusable `/<name>` skill (use `$ARGS` for input) |
|
|
106
110
|
| `/<name>` | Run a skill |
|
|
107
111
|
| `/loop <count> <prompt>` | Repeat a prompt N times (Esc stops) |
|
|
112
|
+
| `/ratchet <goal>` | Solve across rounds, snapshotting on verifier gains, rolling back regressions. Verifier auto-detects; override with `--verify "<cmd>"` (`--rounds N`, `--fitness auto\|exitcode\|<regex>`) |
|
|
108
113
|
| `/mcp` | List connected MCP servers + their tools |
|
|
109
114
|
| `/rmf bootstrap [families]` | Ingest the NIST SP 800-53 catalog (RMF automation) |
|
|
110
115
|
| `/rmf-control` · `/rmf-categorize` · `/rmf-review` · `/rmf-poam` | Bundled RMF skills |
|
|
@@ -243,6 +248,22 @@ exports the open findings to a deterministic **eMASS POA&M CSV** — Control (fr
|
|
|
243
248
|
the CCI map), Vulnerability Description, `POA&M Status=Ongoing`, Milestone (the
|
|
244
249
|
Fix Text), and Severity (CAT I/II/III → High/Moderate/Low) — no LLM, stdlib only.
|
|
245
250
|
|
|
251
|
+
### FIAR audit-readiness (DoD financial-statement audit)
|
|
252
|
+
|
|
253
|
+
Model a Financial Improvement and Audit Readiness engagement — a seeded key-control
|
|
254
|
+
matrix per business cycle (FBWT, P2P, PP&E, INV, CIVPAY, REIM, FR, ITGC), the five FS
|
|
255
|
+
assertions, a deterministic **evidence-chain validator** (a control can't be called
|
|
256
|
+
effective on an incomplete population→sample→…→GL→assertion trace), findings (NFRs), and
|
|
257
|
+
CAPs. `python -m drydock.fiar new|controls|control|assess|reconcile|package`; skills
|
|
258
|
+
`/fiar-assess /fiar-evidence /fiar-readiness /fiar-cap`.
|
|
259
|
+
|
|
260
|
+
**KSD evidence packaging.** `fiar package <engagement> <out> --evidence-dir <dir>` assembles
|
|
261
|
+
an audit binder — `index.md` + `index.json` mapping every control → assertions → KSDs →
|
|
262
|
+
status → evidence-chain completeness → findings — and **collects the actual evidence files
|
|
263
|
+
each test cited**, zipped. Add `--redact "<term>"` (repeatable) to **red-box** names/secrets
|
|
264
|
+
in the evidence *and* the manifest for a releasable version (verified via the Document
|
|
265
|
+
Canvas); the original engagement is never touched. Also exposed as the `FiarPackage` tool.
|
|
266
|
+
|
|
246
267
|
### Document Canvas — editing documents far larger than the context window
|
|
247
268
|
|
|
248
269
|
Edit a 300-, 800-, 1000-page document the way you edit a large codebase: **search,
|
|
@@ -327,6 +348,54 @@ docker run -it --add-host=host.docker.internal:host-gateway \
|
|
|
327
348
|
`-v "$PWD:/work"` mounts your project so the agent can read/edit it. Tags:
|
|
328
349
|
`fbobe3/drydock:latest` and `fbobe3/drydock:<version>` (e.g. `:3.0.135`).
|
|
329
350
|
|
|
351
|
+
### Serving Gemma-4-31B
|
|
352
|
+
|
|
353
|
+
Drydock is provider-agnostic, but its primary target is dense **Gemma-4-31B**.
|
|
354
|
+
Two proven ways to serve it on a 2×GPU box, both exposing an OpenAI-compatible
|
|
355
|
+
API on `:8000` as model `gemma4`:
|
|
356
|
+
|
|
357
|
+
**llama.cpp** — QAT GGUF, flexible concurrency (more slots if you need them):
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
llama-server -m gemma-4-31B-it-qat-UD-Q4_K_XL.gguf \
|
|
361
|
+
-c 65536 -np 2 --host 0.0.0.0 --port 8000 --alias gemma4
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**vLLM** — w4a16 QAT, ~2× faster per request, 128K context:
|
|
365
|
+
|
|
366
|
+
```
|
|
367
|
+
docker run -d --name vllm-prod --gpus all --ipc=host --restart unless-stopped \
|
|
368
|
+
-v /data3/Models:/models -p 8000:8000 \
|
|
369
|
+
-e NCCL_P2P_DISABLE=1 \
|
|
370
|
+
vllm/vllm-openai:v0.26.0 \
|
|
371
|
+
--model /models/gemma-4-31B-it-qat-w4a16-ct \
|
|
372
|
+
--served-model-name gemma4 \
|
|
373
|
+
--tensor-parallel-size 2 \
|
|
374
|
+
--max-model-len 131072 \
|
|
375
|
+
--max-num-seqs 2 \
|
|
376
|
+
--gpu-memory-utilization 0.97 \
|
|
377
|
+
--kv-cache-dtype fp8 \
|
|
378
|
+
--tool-call-parser gemma4 \
|
|
379
|
+
--enable-auto-tool-choice
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Point drydock at either with
|
|
383
|
+
`--provider vllm --base-url http://<host>:8000/v1 --model gemma4`.
|
|
384
|
+
|
|
385
|
+
**vLLM-on-Gemma-4 notes** (drydock handles these for you as of **3.1.7**):
|
|
386
|
+
|
|
387
|
+
- **`skip_special_tokens: false`** is sent on every request — without it, a turn
|
|
388
|
+
truncated at `max_tokens` (tool-call / reasoning tails) comes back with *empty*
|
|
389
|
+
content, which looks like a refusal. If `finish_reason == "length"`, retry with
|
|
390
|
+
a larger `max_tokens` (≥ 2000 for reasoning-heavy calls).
|
|
391
|
+
- **Reasoning is off by default.** Enable per-request via
|
|
392
|
+
`chat_template_kwargs.enable_thinking`; the vLLM reasoning parser is disabled,
|
|
393
|
+
so `<|channel>thought …` markers arrive inside `content` (stripped client-side).
|
|
394
|
+
- **`--max-num-seqs 2`** caps concurrency at 2 — the cost of fitting 128K context
|
|
395
|
+
into the VRAM. Use llama.cpp if you need more concurrent sessions than context.
|
|
396
|
+
- Tool-calling is the standard OpenAI format; `/props` is llama.cpp-only (vLLM
|
|
397
|
+
404s it — drydock falls back to `/v1/models` for the context length).
|
|
398
|
+
|
|
330
399
|
## Using it
|
|
331
400
|
|
|
332
401
|
Type a task and press **Enter**. Drydock reads/writes/edits files and runs
|
|
@@ -20,6 +20,9 @@ not render an audit judgement.
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
import json
|
|
23
|
+
import re
|
|
24
|
+
import shutil
|
|
25
|
+
import zipfile
|
|
23
26
|
from dataclasses import dataclass, field, asdict
|
|
24
27
|
from datetime import date
|
|
25
28
|
from pathlib import Path
|
|
@@ -466,6 +469,139 @@ def new_engagement(name: str, cycle: str, wave: int | None = None) -> Engagement
|
|
|
466
469
|
})
|
|
467
470
|
|
|
468
471
|
|
|
472
|
+
# ── KSD evidence package (audit binder) ────────────────────────────────────
|
|
473
|
+
# Assemble the Key Supporting Documents behind an engagement into a reviewable
|
|
474
|
+
# package: a manifest (control → assertion → KSDs → status → evidence chain →
|
|
475
|
+
# findings) plus the actual evidence files, zipped. Optionally red-box text
|
|
476
|
+
# evidence for a releasable version. Deterministic, stdlib only.
|
|
477
|
+
_FILENAME_RE = re.compile(r"[\w][\w./-]*\.[A-Za-z0-9]{1,6}")
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _referenced_files(control: dict, evidence_dir: Path | None) -> list[str]:
|
|
481
|
+
"""Filenames named in the control's evidence / chain text that actually exist
|
|
482
|
+
in evidence_dir (so the binder collects the KSDs the test cited)."""
|
|
483
|
+
if not evidence_dir or not evidence_dir.is_dir():
|
|
484
|
+
return []
|
|
485
|
+
avail = {p.name: p for p in evidence_dir.iterdir() if p.is_file()}
|
|
486
|
+
text = " ".join([str(control.get("evidence", "")),
|
|
487
|
+
*[str(v) for v in (control.get("evidence_chain") or {}).values()]])
|
|
488
|
+
found: list[str] = []
|
|
489
|
+
for m in _FILENAME_RE.findall(text):
|
|
490
|
+
name = Path(m).name
|
|
491
|
+
if name in avail and name not in found:
|
|
492
|
+
found.append(name)
|
|
493
|
+
return found
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def build_ksd_package(engagement_path: str | Path, out_dir: str | Path, *,
|
|
497
|
+
evidence_dir: str | Path | None = None, cycle: str | None = None,
|
|
498
|
+
status: str | None = None, redact: list[str] | None = None) -> dict:
|
|
499
|
+
"""Build a KSD evidence package from an engagement. Collects the cited evidence
|
|
500
|
+
files (from evidence_dir), writes index.json + index.md, optionally red-boxes
|
|
501
|
+
text evidence, and zips the binder. Returns a summary dict."""
|
|
502
|
+
eng = Engagement.load(engagement_path)
|
|
503
|
+
out = Path(out_dir)
|
|
504
|
+
(out / "evidence").mkdir(parents=True, exist_ok=True)
|
|
505
|
+
ev_dir = Path(evidence_dir) if evidence_dir else None
|
|
506
|
+
redact_terms = [t for t in (redact or []) if t.strip()]
|
|
507
|
+
|
|
508
|
+
findings_by_control: dict[str, list] = {}
|
|
509
|
+
for f in eng._d.get("findings", []):
|
|
510
|
+
findings_by_control.setdefault(f.get("control_id", ""), []).append(f)
|
|
511
|
+
|
|
512
|
+
controls = list(eng._d.get("controls", []))
|
|
513
|
+
if cycle:
|
|
514
|
+
controls = [c for c in controls if str(c.get("cycle", "")).upper() == cycle.upper()]
|
|
515
|
+
if status:
|
|
516
|
+
controls = [c for c in controls if c.get("status") == status]
|
|
517
|
+
|
|
518
|
+
entries = []
|
|
519
|
+
collected = 0
|
|
520
|
+
redactions = []
|
|
521
|
+
for c in controls:
|
|
522
|
+
cid = c["id"]
|
|
523
|
+
copied = []
|
|
524
|
+
for name in _referenced_files(c, ev_dir):
|
|
525
|
+
dest = out / "evidence" / cid
|
|
526
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
527
|
+
dst = dest / name
|
|
528
|
+
shutil.copy(ev_dir / name, dst) # type: ignore[arg-type]
|
|
529
|
+
if redact_terms and dst.suffix.lower() in (".txt", ".md", ".markdown"):
|
|
530
|
+
from drydock import doccanvas as dc
|
|
531
|
+
fmt = "text" if dst.suffix.lower() == ".txt" else "markdown"
|
|
532
|
+
doc = dc.parse(dst.read_text("utf-8", "replace"), fmt, str(dst))
|
|
533
|
+
n = 0
|
|
534
|
+
for term in redact_terms:
|
|
535
|
+
try:
|
|
536
|
+
n += dc.redact(doc, query=term)["redacted"]
|
|
537
|
+
except dc.PatchError:
|
|
538
|
+
pass
|
|
539
|
+
if n:
|
|
540
|
+
dst.write_text(dc.render(doc), "utf-8")
|
|
541
|
+
redactions.append({"file": f"{cid}/{name}", "redacted": n})
|
|
542
|
+
copied.append(name)
|
|
543
|
+
collected += 1
|
|
544
|
+
entries.append({
|
|
545
|
+
"id": cid, "cycle": c.get("cycle"), "objective": c.get("objective"),
|
|
546
|
+
"assertions": c.get("assertions"), "status": c.get("status", "not_tested"),
|
|
547
|
+
"ksds": c.get("ksds", []), "evidence": c.get("evidence", ""),
|
|
548
|
+
"evidence_chain": validate_chain(c.get("evidence_chain")),
|
|
549
|
+
"evidence_files": copied,
|
|
550
|
+
"findings": [{"id": f.get("id"), "severity": f.get("severity"),
|
|
551
|
+
"condition": f.get("condition")} for f in findings_by_control.get(cid, [])],
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
# For a releasable package, scrub the redaction terms from the MANIFEST too
|
|
555
|
+
# (not just the collected files) so a name/secret can't leak via the binder.
|
|
556
|
+
if redact_terms:
|
|
557
|
+
marker = "[REDACTED]"
|
|
558
|
+
for e in entries:
|
|
559
|
+
for term in redact_terms:
|
|
560
|
+
e["evidence"] = e["evidence"].replace(term, marker)
|
|
561
|
+
for f in e["findings"]:
|
|
562
|
+
f["condition"] = (f.get("condition") or "").replace(term, marker)
|
|
563
|
+
|
|
564
|
+
manifest = {
|
|
565
|
+
"engagement": eng.name, "assessable_unit": eng.cycle, "wave": eng.wave,
|
|
566
|
+
"generated": date.today().isoformat(), "source": Path(engagement_path).name,
|
|
567
|
+
"control_count": len(entries), "evidence_files_collected": collected,
|
|
568
|
+
"redactions": redactions, "controls": entries,
|
|
569
|
+
}
|
|
570
|
+
(out / "index.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
|
|
571
|
+
(out / "index.md").write_text(_ksd_index_md(manifest), encoding="utf-8")
|
|
572
|
+
|
|
573
|
+
zip_path = str(out) + ".zip"
|
|
574
|
+
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as z:
|
|
575
|
+
for p in sorted(out.rglob("*")):
|
|
576
|
+
if p.is_file():
|
|
577
|
+
z.write(p, p.relative_to(out.parent))
|
|
578
|
+
return {"package": str(out), "zip": zip_path, "controls": len(entries),
|
|
579
|
+
"files_collected": collected, "redactions": len(redactions),
|
|
580
|
+
"missing_evidence": [e["id"] for e in entries if not e["evidence_files"]]}
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def _ksd_index_md(m: dict) -> str:
|
|
584
|
+
lines = [f"# KSD Evidence Package — {m['engagement']}",
|
|
585
|
+
f"Assessable unit: {m['assessable_unit']} · Wave {m['wave']} · "
|
|
586
|
+
f"generated {m['generated']} from {m['source']}",
|
|
587
|
+
f"Controls: {m['control_count']} · evidence files collected: "
|
|
588
|
+
f"{m['evidence_files_collected']} · redactions: {len(m['redactions'])}", ""]
|
|
589
|
+
for e in m["controls"]:
|
|
590
|
+
lines.append(f"## {e['id']} — {e['objective']}")
|
|
591
|
+
lines.append(f"- Cycle: {e['cycle']} · Assertions: {', '.join(e['assertions'] or [])}"
|
|
592
|
+
f" · Status: **{e['status']}**")
|
|
593
|
+
lines.append(f"- KSDs required: {', '.join(e['ksds']) or '(none listed)'}")
|
|
594
|
+
ch = e["evidence_chain"]
|
|
595
|
+
lines.append(f"- Evidence chain: {'COMPLETE' if ch['complete'] else 'INCOMPLETE — missing ' + ', '.join(ch['missing'])}")
|
|
596
|
+
if e["evidence"]:
|
|
597
|
+
lines.append(f"- Evidence examined: {e['evidence']}")
|
|
598
|
+
lines.append(f"- Evidence files: {', '.join('evidence/'+e['id']+'/'+f for f in e['evidence_files']) or '⚠ NONE collected'}")
|
|
599
|
+
for f in e["findings"]:
|
|
600
|
+
lines.append(f"- FINDING {f['id']} ({f['severity']}): {f['condition']}")
|
|
601
|
+
lines.append("")
|
|
602
|
+
return "\n".join(lines)
|
|
603
|
+
|
|
604
|
+
|
|
469
605
|
# ── CLI ───────────────────────────────────────────────────────────────────
|
|
470
606
|
def _main(argv: list[str]) -> int:
|
|
471
607
|
import argparse
|
|
@@ -482,6 +618,9 @@ def _main(argv: list[str]) -> int:
|
|
|
482
618
|
q = sub.add_parser("reconcile"); q.add_argument("entity", type=float)
|
|
483
619
|
q.add_argument("source", type=float); q.add_argument("--tolerance", type=float, default=0.0)
|
|
484
620
|
q = sub.add_parser("cycles")
|
|
621
|
+
q = sub.add_parser("package"); q.add_argument("path"); q.add_argument("out")
|
|
622
|
+
q.add_argument("--evidence-dir"); q.add_argument("--cycle"); q.add_argument("--status")
|
|
623
|
+
q.add_argument("--redact", action="append", default=[])
|
|
485
624
|
a = p.parse_args(argv)
|
|
486
625
|
|
|
487
626
|
if a.cmd == "cycles":
|
|
@@ -491,6 +630,15 @@ def _main(argv: list[str]) -> int:
|
|
|
491
630
|
if a.cmd == "reconcile":
|
|
492
631
|
r = reconcile(a.entity, a.source, tolerance=a.tolerance)
|
|
493
632
|
print(json.dumps(r, indent=2)); return 0 if r["reconciled"] else 1
|
|
633
|
+
if a.cmd == "package":
|
|
634
|
+
r = build_ksd_package(a.path, a.out, evidence_dir=a.evidence_dir,
|
|
635
|
+
cycle=a.cycle, status=a.status, redact=a.redact)
|
|
636
|
+
print(f"KSD package: {r['controls']} controls, {r['files_collected']} evidence file(s)"
|
|
637
|
+
+ (f", {r['redactions']} file(s) redacted" if r["redactions"] else "")
|
|
638
|
+
+ f" → {r['package']}/ (+ {Path(r['zip']).name})")
|
|
639
|
+
if r["missing_evidence"]:
|
|
640
|
+
print(f"⚠ no evidence file collected for: {', '.join(r['missing_evidence'])}")
|
|
641
|
+
return 0
|
|
494
642
|
if a.cmd == "new":
|
|
495
643
|
eng = new_engagement(a.name, a.cycle, wave=a.wave); eng.save(a.path)
|
|
496
644
|
print(f"created {a.path}: {eng.name} / {eng.cycle} (Wave {eng.wave}) "
|
|
@@ -584,6 +584,17 @@ def stream(
|
|
|
584
584
|
if config.get("temperature") is not None:
|
|
585
585
|
kwargs["temperature"] = config["temperature"]
|
|
586
586
|
|
|
587
|
+
# vLLM-Gemma quirk: a turn truncated at max_tokens (esp. tool-call / reasoning
|
|
588
|
+
# turns, whose tail is special tokens) comes back with EMPTY content unless we
|
|
589
|
+
# send skip_special_tokens=false — and a checker-scored harness reads an empty
|
|
590
|
+
# turn as a FAILURE. Always send it for vLLM (harmless / ignored on llama.cpp),
|
|
591
|
+
# and floor an already-configured-but-tiny budget so reasoning turns don't
|
|
592
|
+
# truncate to nothing. We never impose a cap where none was set.
|
|
593
|
+
if provider == "vllm":
|
|
594
|
+
kwargs.setdefault("extra_body", {})["skip_special_tokens"] = False
|
|
595
|
+
if kwargs.get("max_tokens") and kwargs["max_tokens"] < 2000:
|
|
596
|
+
kwargs["max_tokens"] = 2000
|
|
597
|
+
|
|
587
598
|
# Adaptive reasoning budget (harmony/gpt-oss style models accept this;
|
|
588
599
|
# endpoints without the knob ignore the extra field). Set per turn by the
|
|
589
600
|
# agent loop: high for planning, low for routine continuation.
|