drydock-cli 3.1.10__tar.gz → 3.1.12__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.10/drydock_cli.egg-info → drydock_cli-3.1.12}/PKG-INFO +19 -11
  2. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/README.md +18 -10
  3. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/cli.py +6 -3
  4. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/config.py +55 -23
  5. {drydock_cli-3.1.10 → drydock_cli-3.1.12/drydock_cli.egg-info}/PKG-INFO +19 -11
  6. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/pyproject.toml +1 -1
  7. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_config.py +40 -0
  8. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/LICENSE +0 -0
  9. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/NOTICE +0 -0
  10. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/__init__.py +0 -0
  11. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/__main__.py +0 -0
  12. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/advisor.py +0 -0
  13. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/agent.py +0 -0
  14. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/bash_safety.py +0 -0
  15. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/budget.py +0 -0
  16. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/__init__.py +0 -0
  17. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/document-canvas.md +0 -0
  18. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/fiar-assess.md +0 -0
  19. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/fiar-cap.md +0 -0
  20. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/fiar-evidence.md +0 -0
  21. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/fiar-readiness.md +0 -0
  22. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/ml-data.md +0 -0
  23. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/ml-debug.md +0 -0
  24. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/ml-finetune.md +0 -0
  25. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/ml-metrics.md +0 -0
  26. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/ml-rl.md +0 -0
  27. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/ml-train.md +0 -0
  28. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/nist-ai-rmf.md +0 -0
  29. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/nist-csf.md +0 -0
  30. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/rmf-categorize.md +0 -0
  31. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/rmf-control.md +0 -0
  32. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/rmf-poam.md +0 -0
  33. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/rmf-review.md +0 -0
  34. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/stig-assess.md +0 -0
  35. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/builtin_skills/stig-remediate.md +0 -0
  36. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/cci.py +0 -0
  37. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/compaction.py +0 -0
  38. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/detect.py +0 -0
  39. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/doccanvas.py +0 -0
  40. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/events.py +0 -0
  41. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/extract.py +0 -0
  42. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/fiar.py +0 -0
  43. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/gittools.py +0 -0
  44. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/graphrag.py +0 -0
  45. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/guards.py +0 -0
  46. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/loop_detect.py +0 -0
  47. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/mcp.py +0 -0
  48. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/phases.py +0 -0
  49. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/poam.py +0 -0
  50. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/progress.py +0 -0
  51. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/providers.py +0 -0
  52. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/ratchet.py +0 -0
  53. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/recipes.py +0 -0
  54. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/recovery.py +0 -0
  55. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/resume.py +0 -0
  56. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/rmf.py +0 -0
  57. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/rmf_graph.py +0 -0
  58. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/skills.py +0 -0
  59. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/stig.py +0 -0
  60. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/subagents.py +0 -0
  61. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/suggest.py +0 -0
  62. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/task_state.py +0 -0
  63. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/tool_policy.py +0 -0
  64. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/tool_registry.py +0 -0
  65. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/tool_result.py +0 -0
  66. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/tool_select.py +0 -0
  67. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/tool_validate.py +0 -0
  68. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/tools/__init__.py +0 -0
  69. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/trajectory.py +0 -0
  70. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/tui/__init__.py +0 -0
  71. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/tui/app.py +0 -0
  72. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/tui/approval.py +0 -0
  73. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/tui/messages.py +0 -0
  74. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/tui/widgets.py +0 -0
  75. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/tuning.py +0 -0
  76. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/verification.py +0 -0
  77. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock/web.py +0 -0
  78. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock_cli.egg-info/SOURCES.txt +0 -0
  79. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock_cli.egg-info/dependency_links.txt +0 -0
  80. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock_cli.egg-info/entry_points.txt +0 -0
  81. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock_cli.egg-info/requires.txt +0 -0
  82. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/drydock_cli.egg-info/top_level.txt +0 -0
  83. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/setup.cfg +0 -0
  84. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_advisor.py +0 -0
  85. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_approval.py +0 -0
  86. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_back_command.py +0 -0
  87. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_bash_background.py +0 -0
  88. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_bash_binary_output.py +0 -0
  89. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_bash_crossplatform.py +0 -0
  90. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_bash_output_bounding.py +0 -0
  91. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_bash_process_group.py +0 -0
  92. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_bash_safety.py +0 -0
  93. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_bash_sanitize.py +0 -0
  94. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_bash_shell.py +0 -0
  95. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_bash_stdin.py +0 -0
  96. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_bash_stop_partial.py +0 -0
  97. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_bash_timeout_network.py +0 -0
  98. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_bash_timeout_param.py +0 -0
  99. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_budget.py +0 -0
  100. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_cci.py +0 -0
  101. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_cli_agents.py +0 -0
  102. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_compact_command.py +0 -0
  103. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_compaction.py +0 -0
  104. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_config_migration.py +0 -0
  105. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_context_limit_config.py +0 -0
  106. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_context_limit_issue25.py +0 -0
  107. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_criteria_coverage.py +0 -0
  108. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_cycling_detection.py +0 -0
  109. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_degenerate_argument.py +0 -0
  110. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_detect.py +0 -0
  111. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_dispatch.py +0 -0
  112. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_doccanvas.py +0 -0
  113. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_e2e_connected.py +0 -0
  114. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_edit_replace_all.py +0 -0
  115. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_effort_governor.py +0 -0
  116. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_empty_response.py +0 -0
  117. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_events.py +0 -0
  118. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_extract.py +0 -0
  119. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_failure_loop.py +0 -0
  120. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_fiar.py +0 -0
  121. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_first_run_setup.py +0 -0
  122. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_gittools.py +0 -0
  123. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_glob_edit_edges.py +0 -0
  124. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_graphify_example.py +0 -0
  125. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_graphrag.py +0 -0
  126. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_graphrag_quoted_path.py +0 -0
  127. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_graphrag_sqlite.py +0 -0
  128. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_grep_and_read_robust.py +0 -0
  129. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_guards_and_tools.py +0 -0
  130. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_hallucinated_tools.py +0 -0
  131. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_leaked_tool_call.py +0 -0
  132. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_loop_detect.py +0 -0
  133. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_mcp.py +0 -0
  134. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_model_registry.py +0 -0
  135. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_oneshot_unreachable.py +0 -0
  136. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_overthink_interrupt.py +0 -0
  137. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_phases.py +0 -0
  138. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_plan_autocontinue.py +0 -0
  139. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_poam.py +0 -0
  140. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_progress.py +0 -0
  141. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_providers_unreachable.py +0 -0
  142. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_ratchet.py +0 -0
  143. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_read_index.py +0 -0
  144. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_recipes.py +0 -0
  145. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_recovery.py +0 -0
  146. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_recovery_config.py +0 -0
  147. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_recovery_integration.py +0 -0
  148. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_repeated_outcome.py +0 -0
  149. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_repetition_interrupt.py +0 -0
  150. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_resume_events.py +0 -0
  151. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_resume_restore.py +0 -0
  152. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_resume_snapshot.py +0 -0
  153. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_rmf.py +0 -0
  154. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_rmf_graph.py +0 -0
  155. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_rmf_stig_graph.py +0 -0
  156. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_rolling_plan.py +0 -0
  157. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_runaway_repetition.py +0 -0
  158. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_screenshot.py +0 -0
  159. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_server_probe.py +0 -0
  160. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_skills.py +0 -0
  161. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_sqlite_events.py +0 -0
  162. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_stall_retry.py +0 -0
  163. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_stig.py +0 -0
  164. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_stop.py +0 -0
  165. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_streaming_newlines.py +0 -0
  166. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_subagent.py +0 -0
  167. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_subagents.py +0 -0
  168. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_suggest.py +0 -0
  169. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_system_prompt_help.py +0 -0
  170. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_task_state.py +0 -0
  171. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_timeline.py +0 -0
  172. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_todo.py +0 -0
  173. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_tool_arg_coercion.py +0 -0
  174. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_tool_arg_coercion_more.py +0 -0
  175. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_tool_arg_parsing.py +0 -0
  176. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_tool_canonical.py +0 -0
  177. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_tool_policy.py +0 -0
  178. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_tool_result.py +0 -0
  179. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_tool_select.py +0 -0
  180. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_tool_validate.py +0 -0
  181. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_tools_undo.py +0 -0
  182. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_trajectory.py +0 -0
  183. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_tui.py +0 -0
  184. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_tuning.py +0 -0
  185. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_verification_gate.py +0 -0
  186. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_viewimage.py +0 -0
  187. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_vision_input.py +0 -0
  188. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_web_tools.py +0 -0
  189. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_windows_shell.py +0 -0
  190. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_worker_subagent.py +0 -0
  191. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/tests/test_write_content_coerce.py +0 -0
  192. {drydock_cli-3.1.10 → drydock_cli-3.1.12}/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.10
3
+ Version: 3.1.12
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
@@ -450,16 +450,24 @@ conventions.
450
450
  ### Custom system prompt
451
451
 
452
452
  For standing instructions applied on **every** turn (stronger than the
453
- per-project `AGENTS.md`, which is framed as optional background), edit
454
- **`~/.drydock/system_prompt.md`**. Drydock creates this file for you (a
455
- commented template) on first run, so you never have to know where it lives —
456
- just open it and write your instructions. It has no effect until you add text
457
- (the template is all comments, which are ignored).
458
-
459
- Prefer keeping it in the config file instead? Set `system_prompt = "..."` in
460
- `~/.drydock/config.toml`. The `system_prompt.md` file wins if both are set;
461
- contents are capped at 8000 chars, injected after drydock's base prompt and
462
- before any project `AGENTS.md`, and apply in both the TUI and CLI.
453
+ per-project `AGENTS.md`, which is framed as optional background), edit a
454
+ `system_prompt.md` file. Drydock creates a commented template for you, so you
455
+ never have to know where it lives — just open it and write your instructions.
456
+ It has no effect until you add text (the template is all comments, ignored).
457
+
458
+ Two scopes, most-specific wins:
459
+
460
+ - **Per-project** — `system_prompt.md` in the folder you launch drydock from.
461
+ Auto-created inside any **git repo** (so different projects get different
462
+ standing orders, and drydock never drops the file into non-project dirs).
463
+ - **Global** — `~/.drydock/system_prompt.md`. Auto-created on first run; applies
464
+ to every project. A per-project file **overrides** it.
465
+ - **Or the config key** — `system_prompt = "..."` in `~/.drydock/config.toml`
466
+ (lowest precedence).
467
+
468
+ Contents are capped at 8000 chars, injected after drydock's base prompt and
469
+ before any project `AGENTS.md`, and apply in both the TUI and CLI. Restart
470
+ drydock after editing.
463
471
 
464
472
  ## Safety
465
473
 
@@ -426,16 +426,24 @@ conventions.
426
426
  ### Custom system prompt
427
427
 
428
428
  For standing instructions applied on **every** turn (stronger than the
429
- per-project `AGENTS.md`, which is framed as optional background), edit
430
- **`~/.drydock/system_prompt.md`**. Drydock creates this file for you (a
431
- commented template) on first run, so you never have to know where it lives —
432
- just open it and write your instructions. It has no effect until you add text
433
- (the template is all comments, which are ignored).
434
-
435
- Prefer keeping it in the config file instead? Set `system_prompt = "..."` in
436
- `~/.drydock/config.toml`. The `system_prompt.md` file wins if both are set;
437
- contents are capped at 8000 chars, injected after drydock's base prompt and
438
- before any project `AGENTS.md`, and apply in both the TUI and CLI.
429
+ per-project `AGENTS.md`, which is framed as optional background), edit a
430
+ `system_prompt.md` file. Drydock creates a commented template for you, so you
431
+ never have to know where it lives — just open it and write your instructions.
432
+ It has no effect until you add text (the template is all comments, ignored).
433
+
434
+ Two scopes, most-specific wins:
435
+
436
+ - **Per-project** — `system_prompt.md` in the folder you launch drydock from.
437
+ Auto-created inside any **git repo** (so different projects get different
438
+ standing orders, and drydock never drops the file into non-project dirs).
439
+ - **Global** — `~/.drydock/system_prompt.md`. Auto-created on first run; applies
440
+ to every project. A per-project file **overrides** it.
441
+ - **Or the config key** — `system_prompt = "..."` in `~/.drydock/config.toml`
442
+ (lowest precedence).
443
+
444
+ Contents are capped at 8000 chars, injected after drydock's base prompt and
445
+ before any project `AGENTS.md`, and apply in both the TUI and CLI. Restart
446
+ drydock after editing.
439
447
 
440
448
  ## Safety
441
449
 
@@ -13,7 +13,7 @@ import sys
13
13
  from pathlib import Path
14
14
 
15
15
  from drydock import __version__
16
- from drydock.config import load_user_system_prompt
16
+ from drydock.config import load_user_system_prompt, ensure_project_system_prompt
17
17
  from drydock.agent import AgentState, run, TextChunk, ToolStart, ToolEnd, TurnDone
18
18
 
19
19
 
@@ -436,8 +436,11 @@ def main():
436
436
  # greeting into a runaway build. The TUI reads this from config; CLI modes
437
437
  # call load_project_instructions directly (see run_interactive/run_oneshot).
438
438
  config["project_instructions"] = load_project_instructions()
439
- # The user's custom system prompt (~/.drydock/system_prompt.md or the
440
- # `system_prompt` config key) — standing instructions the TUI injects every turn.
439
+ # In a git project, drop a discoverable per-project prompt template at
440
+ # <project>/.drydock/system_prompt.md (inert until edited). Then load the
441
+ # effective custom prompt — project file OVERRIDES the global ~/.drydock one —
442
+ # as standing instructions the TUI/CLI inject every turn.
443
+ ensure_project_system_prompt()
441
444
  config["user_system_prompt"] = load_user_system_prompt(config)
442
445
 
443
446
  # Connect any configured MCP servers and register their tools so the model
@@ -120,14 +120,21 @@ def default_config_path() -> Path:
120
120
 
121
121
 
122
122
  def system_prompt_path() -> Path:
123
- """User's global custom system-prompt file."""
123
+ """User's GLOBAL custom system-prompt file (applies to every project)."""
124
124
  return Path.home() / ".drydock" / "system_prompt.md"
125
125
 
126
126
 
127
- # The template drydock drops at ~/.drydock/system_prompt.md so the file is always
128
- # THERE and discoverable — the user never has to know the path or filename. It's
129
- # ALL comments, so it has ZERO effect until the user writes real text below them
130
- # (load_user_system_prompt strips <!-- --> blocks before use).
127
+ def project_system_prompt_path(cwd: Path | None = None) -> Path:
128
+ """A PER-PROJECT custom system-prompt file: system_prompt.md in the folder
129
+ drydock was started from. Overrides the global one when present — different
130
+ projects, different orders."""
131
+ return (cwd or Path.cwd()) / "system_prompt.md"
132
+
133
+
134
+ # The template drydock drops so the file is always THERE and discoverable — the
135
+ # user never has to know the path or filename. It's ALL comments, so it has ZERO
136
+ # effect until the user writes real text below them (load_user_system_prompt
137
+ # strips <!-- --> blocks before use).
131
138
  _SYSTEM_PROMPT_TEMPLATE = """\
132
139
  <!--
133
140
  Drydock custom system prompt.
@@ -139,6 +146,10 @@ _SYSTEM_PROMPT_TEMPLATE = """\
139
146
  - Prefer the standard library; avoid adding new dependencies.
140
147
  - State your plan in one sentence before making changes.
141
148
 
149
+ SCOPE: a system_prompt.md in a project's root folder is THAT PROJECT'S prompt;
150
+ the copy at ~/.drydock/system_prompt.md applies to every project. A project
151
+ file OVERRIDES the global one.
152
+
142
153
  This template has NO effect until you add your own text below this comment.
143
154
  Comment blocks like this one are ignored. Capped at 8000 characters.
144
155
  Restart drydock after editing for changes to take effect.
@@ -151,13 +162,19 @@ def _strip_comments(text: str) -> str:
151
162
  return re.sub(r"<!--.*?-->", "", text, flags=re.DOTALL)
152
163
 
153
164
 
154
- def ensure_system_prompt_file(config_dir: Path | None = None) -> None:
155
- """Create <config_dir>/system_prompt.md (the commented template) if it's absent,
156
- so users discover the feature without knowing the path/name. `config_dir`
157
- defaults to ~/.drydock. NEVER overwrites an existing file (can't clobber a real
158
- prompt) and never raises."""
165
+ def _read_prompt_file(p: Path) -> str:
166
+ """Comment-stripped, whitespace-trimmed contents of a prompt file, or ''."""
167
+ try:
168
+ if p.exists():
169
+ return _strip_comments(p.read_text()).strip()
170
+ except OSError:
171
+ pass
172
+ return ""
173
+
174
+
175
+ def _write_template(p: Path) -> None:
176
+ """Write the commented template to p if absent. Never overwrites; never raises."""
159
177
  try:
160
- p = (config_dir / "system_prompt.md") if config_dir is not None else system_prompt_path()
161
178
  if not p.exists():
162
179
  p.parent.mkdir(parents=True, exist_ok=True)
163
180
  p.write_text(_SYSTEM_PROMPT_TEMPLATE, encoding="utf-8")
@@ -165,21 +182,36 @@ def ensure_system_prompt_file(config_dir: Path | None = None) -> None:
165
182
  pass
166
183
 
167
184
 
168
- def load_user_system_prompt(config: dict | None = None) -> str:
185
+ def ensure_system_prompt_file(config_dir: Path | None = None) -> None:
186
+ """Create the GLOBAL <config_dir>/system_prompt.md template if absent (config_dir
187
+ defaults to ~/.drydock), so the feature is discoverable. Never overwrites."""
188
+ _write_template((config_dir / "system_prompt.md") if config_dir is not None else system_prompt_path())
189
+
190
+
191
+ def ensure_project_system_prompt(cwd: Path | None = None) -> None:
192
+ """Create system_prompt.md in the folder drydock was started from if absent —
193
+ but ONLY inside a git repo, so drydock never drops a system_prompt.md into
194
+ arbitrary directories (e.g. $HOME, /tmp). A real project you `git init`'d gets
195
+ a discoverable per-project prompt at its root; a throwaway cwd gets nothing.
196
+ Never raises."""
197
+ cwd = cwd or Path.cwd()
198
+ try:
199
+ if (cwd / ".git").exists(): # file OR dir — covers worktrees too
200
+ _write_template(project_system_prompt_path(cwd))
201
+ except OSError:
202
+ pass
203
+
204
+
205
+ def load_user_system_prompt(config: dict | None = None, cwd: Path | None = None) -> str:
169
206
  """The user's custom system prompt, injected every turn as standing orders.
170
207
 
171
- Source (first non-empty wins): the file ~/.drydock/system_prompt.md (comment
172
- blocks stripped, so the shipped template is inert), then the inline
173
- `system_prompt` config key. Wrapped with a clear header and capped at 8000
174
- chars. Returns '' when neither has real content. Never raises.
208
+ Precedence (first with real content wins): the PER-PROJECT file
209
+ <project>/.drydock/system_prompt.md, then the GLOBAL ~/.drydock/system_prompt.md,
210
+ then the inline `system_prompt` config key. Comment blocks are stripped (so the
211
+ shipped template is inert). Capped at 8000 chars. Returns '' when none has real
212
+ content. Never raises.
175
213
  """
176
- text = ""
177
- try:
178
- p = system_prompt_path()
179
- if p.exists():
180
- text = _strip_comments(p.read_text()).strip()
181
- except OSError:
182
- text = ""
214
+ text = _read_prompt_file(project_system_prompt_path(cwd)) or _read_prompt_file(system_prompt_path())
183
215
  if not text and config:
184
216
  text = str(config.get("system_prompt", "") or "").strip()
185
217
  if not text:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.1.10
3
+ Version: 3.1.12
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
@@ -450,16 +450,24 @@ conventions.
450
450
  ### Custom system prompt
451
451
 
452
452
  For standing instructions applied on **every** turn (stronger than the
453
- per-project `AGENTS.md`, which is framed as optional background), edit
454
- **`~/.drydock/system_prompt.md`**. Drydock creates this file for you (a
455
- commented template) on first run, so you never have to know where it lives —
456
- just open it and write your instructions. It has no effect until you add text
457
- (the template is all comments, which are ignored).
458
-
459
- Prefer keeping it in the config file instead? Set `system_prompt = "..."` in
460
- `~/.drydock/config.toml`. The `system_prompt.md` file wins if both are set;
461
- contents are capped at 8000 chars, injected after drydock's base prompt and
462
- before any project `AGENTS.md`, and apply in both the TUI and CLI.
453
+ per-project `AGENTS.md`, which is framed as optional background), edit a
454
+ `system_prompt.md` file. Drydock creates a commented template for you, so you
455
+ never have to know where it lives — just open it and write your instructions.
456
+ It has no effect until you add text (the template is all comments, ignored).
457
+
458
+ Two scopes, most-specific wins:
459
+
460
+ - **Per-project** — `system_prompt.md` in the folder you launch drydock from.
461
+ Auto-created inside any **git repo** (so different projects get different
462
+ standing orders, and drydock never drops the file into non-project dirs).
463
+ - **Global** — `~/.drydock/system_prompt.md`. Auto-created on first run; applies
464
+ to every project. A per-project file **overrides** it.
465
+ - **Or the config key** — `system_prompt = "..."` in `~/.drydock/config.toml`
466
+ (lowest precedence).
467
+
468
+ Contents are capped at 8000 chars, injected after drydock's base prompt and
469
+ before any project `AGENTS.md`, and apply in both the TUI and CLI. Restart
470
+ drydock after editing.
463
471
 
464
472
  ## Safety
465
473
 
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
  # PyPI distribution name is drydock-cli (the established install name, continued
8
8
  # from the retired v2 fork); the import package + CLI command stay `drydock`.
9
9
  name = "drydock-cli"
10
- version = "3.1.10"
10
+ version = "3.1.12"
11
11
  description = "Drydock — a local, provider-agnostic terminal coding agent for local LLMs"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.11"
@@ -159,3 +159,43 @@ def test_load_ignores_comment_only_file(monkeypatch, tmp_path):
159
159
  (d / "system_prompt.md").write_text(cfg._SYSTEM_PROMPT_TEMPLATE)
160
160
  monkeypatch.setattr(cfg.Path, "home", staticmethod(lambda: tmp_path))
161
161
  assert cfg.load_user_system_prompt({}) == ""
162
+
163
+
164
+ # ── per-project system prompt (<project>/.drydock/system_prompt.md) ──
165
+
166
+ def test_project_prompt_overrides_global(monkeypatch, tmp_path):
167
+ home = tmp_path / "home"; (home / ".drydock").mkdir(parents=True)
168
+ (home / ".drydock" / "system_prompt.md").write_text("GLOBAL RULES")
169
+ monkeypatch.setattr(cfg.Path, "home", staticmethod(lambda: home))
170
+ proj = tmp_path / "proj"; proj.mkdir()
171
+ (proj / "system_prompt.md").write_text("PROJECT RULES")
172
+ out = cfg.load_user_system_prompt({}, cwd=proj)
173
+ assert "PROJECT RULES" in out
174
+ assert "GLOBAL RULES" not in out
175
+
176
+
177
+ def test_global_used_when_no_project_file(monkeypatch, tmp_path):
178
+ home = tmp_path / "home"; (home / ".drydock").mkdir(parents=True)
179
+ (home / ".drydock" / "system_prompt.md").write_text("GLOBAL RULES")
180
+ monkeypatch.setattr(cfg.Path, "home", staticmethod(lambda: home))
181
+ proj = tmp_path / "proj"; proj.mkdir() # no project system_prompt.md
182
+ out = cfg.load_user_system_prompt({}, cwd=proj)
183
+ assert "GLOBAL RULES" in out
184
+
185
+
186
+ def test_ensure_project_prompt_only_in_git_repo(tmp_path):
187
+ # git repo → template created at the project root
188
+ repo = tmp_path / "repo"; (repo / ".git").mkdir(parents=True)
189
+ cfg.ensure_project_system_prompt(repo)
190
+ assert (repo / "system_prompt.md").exists()
191
+ # non-git dir → nothing created (no litter)
192
+ plain = tmp_path / "plain"; plain.mkdir()
193
+ cfg.ensure_project_system_prompt(plain)
194
+ assert not (plain / "system_prompt.md").exists()
195
+
196
+
197
+ def test_ensure_project_prompt_never_overwrites(tmp_path):
198
+ repo = tmp_path / "repo"; (repo / ".git").mkdir(parents=True)
199
+ (repo / "system_prompt.md").write_text("MINE")
200
+ cfg.ensure_project_system_prompt(repo)
201
+ assert (repo / "system_prompt.md").read_text() == "MINE"
File without changes
File without changes
File without changes