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.
Files changed (192) hide show
  1. {drydock_cli-3.1.5/drydock_cli.egg-info → drydock_cli-3.1.7}/PKG-INFO +70 -1
  2. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/README.md +69 -0
  3. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/fiar.py +148 -0
  4. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/providers.py +11 -0
  5. drydock_cli-3.1.7/drydock/ratchet.py +522 -0
  6. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tools/__init__.py +56 -1
  7. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tui/app.py +135 -0
  8. {drydock_cli-3.1.5 → drydock_cli-3.1.7/drydock_cli.egg-info}/PKG-INFO +70 -1
  9. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock_cli.egg-info/SOURCES.txt +2 -0
  10. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/pyproject.toml +1 -1
  11. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_doccanvas.py +45 -0
  12. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_fiar.py +57 -0
  13. drydock_cli-3.1.7/tests/test_ratchet.py +324 -0
  14. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/LICENSE +0 -0
  15. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/NOTICE +0 -0
  16. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/__init__.py +0 -0
  17. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/__main__.py +0 -0
  18. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/advisor.py +0 -0
  19. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/agent.py +0 -0
  20. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/bash_safety.py +0 -0
  21. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/budget.py +0 -0
  22. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/__init__.py +0 -0
  23. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/document-canvas.md +0 -0
  24. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/fiar-assess.md +0 -0
  25. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/fiar-cap.md +0 -0
  26. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/fiar-evidence.md +0 -0
  27. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/fiar-readiness.md +0 -0
  28. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/ml-data.md +0 -0
  29. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/ml-debug.md +0 -0
  30. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/ml-finetune.md +0 -0
  31. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/ml-metrics.md +0 -0
  32. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/ml-rl.md +0 -0
  33. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/ml-train.md +0 -0
  34. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/nist-ai-rmf.md +0 -0
  35. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/nist-csf.md +0 -0
  36. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/rmf-categorize.md +0 -0
  37. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/rmf-control.md +0 -0
  38. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/rmf-poam.md +0 -0
  39. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/rmf-review.md +0 -0
  40. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/stig-assess.md +0 -0
  41. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/builtin_skills/stig-remediate.md +0 -0
  42. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/cci.py +0 -0
  43. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/cli.py +0 -0
  44. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/compaction.py +0 -0
  45. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/config.py +0 -0
  46. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/detect.py +0 -0
  47. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/doccanvas.py +0 -0
  48. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/events.py +0 -0
  49. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/extract.py +0 -0
  50. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/gittools.py +0 -0
  51. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/graphrag.py +0 -0
  52. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/guards.py +0 -0
  53. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/loop_detect.py +0 -0
  54. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/mcp.py +0 -0
  55. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/phases.py +0 -0
  56. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/poam.py +0 -0
  57. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/progress.py +0 -0
  58. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/recipes.py +0 -0
  59. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/recovery.py +0 -0
  60. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/resume.py +0 -0
  61. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/rmf.py +0 -0
  62. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/rmf_graph.py +0 -0
  63. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/skills.py +0 -0
  64. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/stig.py +0 -0
  65. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/subagents.py +0 -0
  66. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/suggest.py +0 -0
  67. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/task_state.py +0 -0
  68. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tool_policy.py +0 -0
  69. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tool_registry.py +0 -0
  70. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tool_result.py +0 -0
  71. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tool_select.py +0 -0
  72. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tool_validate.py +0 -0
  73. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/trajectory.py +0 -0
  74. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tui/__init__.py +0 -0
  75. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tui/approval.py +0 -0
  76. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tui/messages.py +0 -0
  77. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tui/widgets.py +0 -0
  78. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/tuning.py +0 -0
  79. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/verification.py +0 -0
  80. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock/web.py +0 -0
  81. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock_cli.egg-info/dependency_links.txt +0 -0
  82. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock_cli.egg-info/entry_points.txt +0 -0
  83. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock_cli.egg-info/requires.txt +0 -0
  84. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/drydock_cli.egg-info/top_level.txt +0 -0
  85. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/setup.cfg +0 -0
  86. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_advisor.py +0 -0
  87. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_approval.py +0 -0
  88. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_back_command.py +0 -0
  89. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_background.py +0 -0
  90. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_binary_output.py +0 -0
  91. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_crossplatform.py +0 -0
  92. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_output_bounding.py +0 -0
  93. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_process_group.py +0 -0
  94. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_safety.py +0 -0
  95. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_sanitize.py +0 -0
  96. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_shell.py +0 -0
  97. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_stdin.py +0 -0
  98. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_stop_partial.py +0 -0
  99. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_timeout_network.py +0 -0
  100. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_bash_timeout_param.py +0 -0
  101. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_budget.py +0 -0
  102. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_cci.py +0 -0
  103. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_cli_agents.py +0 -0
  104. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_compact_command.py +0 -0
  105. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_compaction.py +0 -0
  106. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_config.py +0 -0
  107. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_config_migration.py +0 -0
  108. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_context_limit_config.py +0 -0
  109. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_context_limit_issue25.py +0 -0
  110. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_criteria_coverage.py +0 -0
  111. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_cycling_detection.py +0 -0
  112. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_degenerate_argument.py +0 -0
  113. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_detect.py +0 -0
  114. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_dispatch.py +0 -0
  115. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_e2e_connected.py +0 -0
  116. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_edit_replace_all.py +0 -0
  117. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_effort_governor.py +0 -0
  118. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_empty_response.py +0 -0
  119. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_events.py +0 -0
  120. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_extract.py +0 -0
  121. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_failure_loop.py +0 -0
  122. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_first_run_setup.py +0 -0
  123. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_gittools.py +0 -0
  124. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_glob_edit_edges.py +0 -0
  125. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_graphify_example.py +0 -0
  126. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_graphrag.py +0 -0
  127. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_graphrag_quoted_path.py +0 -0
  128. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_graphrag_sqlite.py +0 -0
  129. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_grep_and_read_robust.py +0 -0
  130. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_guards_and_tools.py +0 -0
  131. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_hallucinated_tools.py +0 -0
  132. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_leaked_tool_call.py +0 -0
  133. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_loop_detect.py +0 -0
  134. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_mcp.py +0 -0
  135. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_model_registry.py +0 -0
  136. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_oneshot_unreachable.py +0 -0
  137. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_overthink_interrupt.py +0 -0
  138. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_phases.py +0 -0
  139. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_plan_autocontinue.py +0 -0
  140. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_poam.py +0 -0
  141. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_progress.py +0 -0
  142. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_providers_unreachable.py +0 -0
  143. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_read_index.py +0 -0
  144. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_recipes.py +0 -0
  145. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_recovery.py +0 -0
  146. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_recovery_config.py +0 -0
  147. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_recovery_integration.py +0 -0
  148. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_repeated_outcome.py +0 -0
  149. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_repetition_interrupt.py +0 -0
  150. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_resume_events.py +0 -0
  151. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_resume_restore.py +0 -0
  152. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_resume_snapshot.py +0 -0
  153. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_rmf.py +0 -0
  154. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_rmf_graph.py +0 -0
  155. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_rmf_stig_graph.py +0 -0
  156. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_rolling_plan.py +0 -0
  157. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_runaway_repetition.py +0 -0
  158. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_screenshot.py +0 -0
  159. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_server_probe.py +0 -0
  160. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_skills.py +0 -0
  161. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_sqlite_events.py +0 -0
  162. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_stall_retry.py +0 -0
  163. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_stig.py +0 -0
  164. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_stop.py +0 -0
  165. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_streaming_newlines.py +0 -0
  166. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_subagent.py +0 -0
  167. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_subagents.py +0 -0
  168. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_suggest.py +0 -0
  169. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_system_prompt_help.py +0 -0
  170. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_task_state.py +0 -0
  171. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_timeline.py +0 -0
  172. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_todo.py +0 -0
  173. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_arg_coercion.py +0 -0
  174. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_arg_coercion_more.py +0 -0
  175. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_arg_parsing.py +0 -0
  176. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_canonical.py +0 -0
  177. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_policy.py +0 -0
  178. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_result.py +0 -0
  179. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_select.py +0 -0
  180. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tool_validate.py +0 -0
  181. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tools_undo.py +0 -0
  182. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_trajectory.py +0 -0
  183. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tui.py +0 -0
  184. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_tuning.py +0 -0
  185. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_verification_gate.py +0 -0
  186. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_viewimage.py +0 -0
  187. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_vision_input.py +0 -0
  188. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_web_tools.py +0 -0
  189. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_windows_shell.py +0 -0
  190. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_worker_subagent.py +0 -0
  191. {drydock_cli-3.1.5 → drydock_cli-3.1.7}/tests/test_write_content_coerce.py +0 -0
  192. {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.5
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.