drydock-cli 3.1.8__tar.gz → 3.1.10__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.8/drydock_cli.egg-info → drydock_cli-3.1.10}/PKG-INFO +15 -1
  2. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/README.md +14 -0
  3. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/cli.py +6 -2
  4. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/config.py +83 -0
  5. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/tui/app.py +9 -4
  6. {drydock_cli-3.1.8 → drydock_cli-3.1.10/drydock_cli.egg-info}/PKG-INFO +15 -1
  7. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/pyproject.toml +1 -1
  8. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_config.py +70 -0
  9. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/LICENSE +0 -0
  10. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/NOTICE +0 -0
  11. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/__init__.py +0 -0
  12. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/__main__.py +0 -0
  13. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/advisor.py +0 -0
  14. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/agent.py +0 -0
  15. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/bash_safety.py +0 -0
  16. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/budget.py +0 -0
  17. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/__init__.py +0 -0
  18. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/document-canvas.md +0 -0
  19. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/fiar-assess.md +0 -0
  20. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/fiar-cap.md +0 -0
  21. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/fiar-evidence.md +0 -0
  22. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/fiar-readiness.md +0 -0
  23. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/ml-data.md +0 -0
  24. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/ml-debug.md +0 -0
  25. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/ml-finetune.md +0 -0
  26. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/ml-metrics.md +0 -0
  27. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/ml-rl.md +0 -0
  28. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/ml-train.md +0 -0
  29. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/nist-ai-rmf.md +0 -0
  30. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/nist-csf.md +0 -0
  31. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/rmf-categorize.md +0 -0
  32. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/rmf-control.md +0 -0
  33. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/rmf-poam.md +0 -0
  34. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/rmf-review.md +0 -0
  35. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/stig-assess.md +0 -0
  36. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/builtin_skills/stig-remediate.md +0 -0
  37. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/cci.py +0 -0
  38. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/compaction.py +0 -0
  39. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/detect.py +0 -0
  40. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/doccanvas.py +0 -0
  41. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/events.py +0 -0
  42. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/extract.py +0 -0
  43. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/fiar.py +0 -0
  44. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/gittools.py +0 -0
  45. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/graphrag.py +0 -0
  46. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/guards.py +0 -0
  47. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/loop_detect.py +0 -0
  48. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/mcp.py +0 -0
  49. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/phases.py +0 -0
  50. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/poam.py +0 -0
  51. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/progress.py +0 -0
  52. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/providers.py +0 -0
  53. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/ratchet.py +0 -0
  54. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/recipes.py +0 -0
  55. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/recovery.py +0 -0
  56. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/resume.py +0 -0
  57. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/rmf.py +0 -0
  58. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/rmf_graph.py +0 -0
  59. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/skills.py +0 -0
  60. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/stig.py +0 -0
  61. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/subagents.py +0 -0
  62. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/suggest.py +0 -0
  63. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/task_state.py +0 -0
  64. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/tool_policy.py +0 -0
  65. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/tool_registry.py +0 -0
  66. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/tool_result.py +0 -0
  67. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/tool_select.py +0 -0
  68. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/tool_validate.py +0 -0
  69. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/tools/__init__.py +0 -0
  70. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/trajectory.py +0 -0
  71. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/tui/__init__.py +0 -0
  72. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/tui/approval.py +0 -0
  73. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/tui/messages.py +0 -0
  74. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/tui/widgets.py +0 -0
  75. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/tuning.py +0 -0
  76. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/verification.py +0 -0
  77. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock/web.py +0 -0
  78. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock_cli.egg-info/SOURCES.txt +0 -0
  79. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock_cli.egg-info/dependency_links.txt +0 -0
  80. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock_cli.egg-info/entry_points.txt +0 -0
  81. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock_cli.egg-info/requires.txt +0 -0
  82. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/drydock_cli.egg-info/top_level.txt +0 -0
  83. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/setup.cfg +0 -0
  84. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_advisor.py +0 -0
  85. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_approval.py +0 -0
  86. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_back_command.py +0 -0
  87. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_bash_background.py +0 -0
  88. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_bash_binary_output.py +0 -0
  89. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_bash_crossplatform.py +0 -0
  90. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_bash_output_bounding.py +0 -0
  91. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_bash_process_group.py +0 -0
  92. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_bash_safety.py +0 -0
  93. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_bash_sanitize.py +0 -0
  94. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_bash_shell.py +0 -0
  95. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_bash_stdin.py +0 -0
  96. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_bash_stop_partial.py +0 -0
  97. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_bash_timeout_network.py +0 -0
  98. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_bash_timeout_param.py +0 -0
  99. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_budget.py +0 -0
  100. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_cci.py +0 -0
  101. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_cli_agents.py +0 -0
  102. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_compact_command.py +0 -0
  103. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_compaction.py +0 -0
  104. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_config_migration.py +0 -0
  105. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_context_limit_config.py +0 -0
  106. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_context_limit_issue25.py +0 -0
  107. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_criteria_coverage.py +0 -0
  108. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_cycling_detection.py +0 -0
  109. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_degenerate_argument.py +0 -0
  110. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_detect.py +0 -0
  111. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_dispatch.py +0 -0
  112. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_doccanvas.py +0 -0
  113. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_e2e_connected.py +0 -0
  114. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_edit_replace_all.py +0 -0
  115. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_effort_governor.py +0 -0
  116. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_empty_response.py +0 -0
  117. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_events.py +0 -0
  118. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_extract.py +0 -0
  119. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_failure_loop.py +0 -0
  120. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_fiar.py +0 -0
  121. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_first_run_setup.py +0 -0
  122. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_gittools.py +0 -0
  123. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_glob_edit_edges.py +0 -0
  124. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_graphify_example.py +0 -0
  125. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_graphrag.py +0 -0
  126. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_graphrag_quoted_path.py +0 -0
  127. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_graphrag_sqlite.py +0 -0
  128. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_grep_and_read_robust.py +0 -0
  129. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_guards_and_tools.py +0 -0
  130. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_hallucinated_tools.py +0 -0
  131. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_leaked_tool_call.py +0 -0
  132. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_loop_detect.py +0 -0
  133. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_mcp.py +0 -0
  134. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_model_registry.py +0 -0
  135. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_oneshot_unreachable.py +0 -0
  136. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_overthink_interrupt.py +0 -0
  137. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_phases.py +0 -0
  138. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_plan_autocontinue.py +0 -0
  139. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_poam.py +0 -0
  140. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_progress.py +0 -0
  141. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_providers_unreachable.py +0 -0
  142. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_ratchet.py +0 -0
  143. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_read_index.py +0 -0
  144. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_recipes.py +0 -0
  145. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_recovery.py +0 -0
  146. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_recovery_config.py +0 -0
  147. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_recovery_integration.py +0 -0
  148. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_repeated_outcome.py +0 -0
  149. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_repetition_interrupt.py +0 -0
  150. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_resume_events.py +0 -0
  151. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_resume_restore.py +0 -0
  152. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_resume_snapshot.py +0 -0
  153. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_rmf.py +0 -0
  154. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_rmf_graph.py +0 -0
  155. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_rmf_stig_graph.py +0 -0
  156. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_rolling_plan.py +0 -0
  157. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_runaway_repetition.py +0 -0
  158. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_screenshot.py +0 -0
  159. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_server_probe.py +0 -0
  160. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_skills.py +0 -0
  161. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_sqlite_events.py +0 -0
  162. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_stall_retry.py +0 -0
  163. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_stig.py +0 -0
  164. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_stop.py +0 -0
  165. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_streaming_newlines.py +0 -0
  166. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_subagent.py +0 -0
  167. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_subagents.py +0 -0
  168. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_suggest.py +0 -0
  169. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_system_prompt_help.py +0 -0
  170. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_task_state.py +0 -0
  171. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_timeline.py +0 -0
  172. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_todo.py +0 -0
  173. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_tool_arg_coercion.py +0 -0
  174. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_tool_arg_coercion_more.py +0 -0
  175. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_tool_arg_parsing.py +0 -0
  176. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_tool_canonical.py +0 -0
  177. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_tool_policy.py +0 -0
  178. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_tool_result.py +0 -0
  179. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_tool_select.py +0 -0
  180. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_tool_validate.py +0 -0
  181. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_tools_undo.py +0 -0
  182. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_trajectory.py +0 -0
  183. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_tui.py +0 -0
  184. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_tuning.py +0 -0
  185. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_verification_gate.py +0 -0
  186. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_viewimage.py +0 -0
  187. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_vision_input.py +0 -0
  188. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_web_tools.py +0 -0
  189. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_windows_shell.py +0 -0
  190. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_worker_subagent.py +0 -0
  191. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/tests/test_write_content_coerce.py +0 -0
  192. {drydock_cli-3.1.8 → drydock_cli-3.1.10}/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.8
3
+ Version: 3.1.10
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
@@ -447,6 +447,20 @@ commands to do the work, showing each as a collapsible tool card.
447
447
  It honors `AGENTS.md` / `DRYDOCK.md` in the working directory for project
448
448
  conventions.
449
449
 
450
+ ### Custom system prompt
451
+
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.
463
+
450
464
  ## Safety
451
465
 
452
466
  Two tiers, plus advisory guards — all designed so legitimate work is never
@@ -423,6 +423,20 @@ commands to do the work, showing each as a collapsible tool card.
423
423
  It honors `AGENTS.md` / `DRYDOCK.md` in the working directory for project
424
424
  conventions.
425
425
 
426
+ ### Custom system prompt
427
+
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.
439
+
426
440
  ## Safety
427
441
 
428
442
  Two tiers, plus advisory guards — all designed so legitimate work is never
@@ -13,6 +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
17
  from drydock.agent import AgentState, run, TextChunk, ToolStart, ToolEnd, TurnDone
17
18
 
18
19
 
@@ -71,7 +72,7 @@ def print_colored(text: str, color: str = "") -> None:
71
72
 
72
73
  def run_interactive(config: dict) -> None:
73
74
  """Interactive REPL mode."""
74
- system = SYSTEM_PROMPT + load_project_instructions()
75
+ system = SYSTEM_PROMPT + load_user_system_prompt(config) + load_project_instructions()
75
76
  state = AgentState()
76
77
 
77
78
  print_colored(f"⚓ DryDock v{__version__} · {config['model']}", "cyan")
@@ -118,7 +119,7 @@ def run_oneshot(prompt: str, config: dict) -> None:
118
119
  """One-shot mode: run a single prompt and exit."""
119
120
  from drydock.providers import LLMUnreachable
120
121
 
121
- system = SYSTEM_PROMPT + load_project_instructions()
122
+ system = SYSTEM_PROMPT + load_user_system_prompt(config) + load_project_instructions()
122
123
  state = AgentState()
123
124
 
124
125
  try:
@@ -435,6 +436,9 @@ def main():
435
436
  # greeting into a runaway build. The TUI reads this from config; CLI modes
436
437
  # call load_project_instructions directly (see run_interactive/run_oneshot).
437
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.
441
+ config["user_system_prompt"] = load_user_system_prompt(config)
438
442
 
439
443
  # Connect any configured MCP servers and register their tools so the model
440
444
  # can call them like native tools. Failures are skipped (never block start);
@@ -13,6 +13,7 @@ All logic original to Drydock.
13
13
  """
14
14
  from __future__ import annotations
15
15
 
16
+ import re
16
17
  import tomllib
17
18
  from pathlib import Path
18
19
 
@@ -106,6 +107,11 @@ DEFAULTS: dict[str, object] = {
106
107
  # (falls back to `model`). Manage with `/model add|default|remove`.
107
108
  "models": [],
108
109
  "default_model": "",
110
+ # A custom system prompt injected into EVERY turn as the user's standing
111
+ # instructions (unlike a per-project AGENTS.md, which is framed as optional
112
+ # background). Two sources, file wins: the file ~/.drydock/system_prompt.md,
113
+ # or this inline key. Empty = nothing added. Capped at 8000 chars.
114
+ "system_prompt": "",
109
115
  }
110
116
 
111
117
 
@@ -113,6 +119,79 @@ def default_config_path() -> Path:
113
119
  return Path.home() / ".drydock" / "config.toml"
114
120
 
115
121
 
122
+ def system_prompt_path() -> Path:
123
+ """User's global custom system-prompt file."""
124
+ return Path.home() / ".drydock" / "system_prompt.md"
125
+
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).
131
+ _SYSTEM_PROMPT_TEMPLATE = """\
132
+ <!--
133
+ Drydock custom system prompt.
134
+
135
+ Whatever you write in this file (OUTSIDE these comment blocks) is added to the
136
+ system prompt on EVERY turn, in both the TUI and CLI — your standing instructions.
137
+ Examples you might add below:
138
+ - Always run the test suite after editing code.
139
+ - Prefer the standard library; avoid adding new dependencies.
140
+ - State your plan in one sentence before making changes.
141
+
142
+ This template has NO effect until you add your own text below this comment.
143
+ Comment blocks like this one are ignored. Capped at 8000 characters.
144
+ Restart drydock after editing for changes to take effect.
145
+ -->
146
+ """
147
+
148
+
149
+ def _strip_comments(text: str) -> str:
150
+ """Drop HTML comment blocks so the shipped template is inert until edited."""
151
+ return re.sub(r"<!--.*?-->", "", text, flags=re.DOTALL)
152
+
153
+
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."""
159
+ try:
160
+ p = (config_dir / "system_prompt.md") if config_dir is not None else system_prompt_path()
161
+ if not p.exists():
162
+ p.parent.mkdir(parents=True, exist_ok=True)
163
+ p.write_text(_SYSTEM_PROMPT_TEMPLATE, encoding="utf-8")
164
+ except OSError:
165
+ pass
166
+
167
+
168
+ def load_user_system_prompt(config: dict | None = None) -> str:
169
+ """The user's custom system prompt, injected every turn as standing orders.
170
+
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.
175
+ """
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 = ""
183
+ if not text and config:
184
+ text = str(config.get("system_prompt", "") or "").strip()
185
+ if not text:
186
+ return ""
187
+ return (
188
+ "\n\n## Custom instructions (from the user's configuration)\n\n"
189
+ "The user has set these standing instructions; follow them throughout "
190
+ "the session in addition to the guidance above.\n\n"
191
+ f"{text[:8000]}"
192
+ )
193
+
194
+
116
195
  # ── Model registry ────────────────────────────────────────────────────────
117
196
  # A model entry is {"name", "base_url", "provider"}. Kept in cfg["models"];
118
197
  # cfg["default_model"] names the launch default. These helpers keep the list
@@ -252,6 +331,10 @@ def resolve(cli_overrides: dict, path: Path | None = None) -> dict:
252
331
  has an editable v3 config with a visible endpoint.
253
332
  """
254
333
  path = path or default_config_path()
334
+ # Drop the discoverable custom-system-prompt template alongside config.toml so
335
+ # the user never has to know its path/name. Inert (all comments) until edited;
336
+ # never overwrites an existing file.
337
+ ensure_system_prompt_file(path.parent)
255
338
  if not path.exists():
256
339
  save_file(dict(DEFAULTS), path)
257
340
  file_cfg = {}
@@ -341,10 +341,15 @@ class DrydockApp(App):
341
341
  return "deny"
342
342
 
343
343
  def _build_system(self, model: str | None) -> str:
344
- # The TUI honors a user's OWN AGENTS.md/DRYDOCK.md like the CLI does —
345
- # cli.main() loads it into config["project_instructions"] as background
346
- # context (drydock never auto-creates one).
347
- return system_prompt_for_model(model) + self.config.get("project_instructions", "")
344
+ # base (model-specific) + the user's custom system prompt (standing orders
345
+ # from ~/.drydock/system_prompt.md or the `system_prompt` config key) + a
346
+ # per-project AGENTS.md/DRYDOCK.md as background context. cli.main() loads
347
+ # both into config; drydock never auto-creates either file.
348
+ return (
349
+ system_prompt_for_model(model)
350
+ + self.config.get("user_system_prompt", "")
351
+ + self.config.get("project_instructions", "")
352
+ )
348
353
 
349
354
  # ── layout ────────────────────────────────────────────────────────────
350
355
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.1.8
3
+ Version: 3.1.10
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
@@ -447,6 +447,20 @@ commands to do the work, showing each as a collapsible tool card.
447
447
  It honors `AGENTS.md` / `DRYDOCK.md` in the working directory for project
448
448
  conventions.
449
449
 
450
+ ### Custom system prompt
451
+
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.
463
+
450
464
  ## Safety
451
465
 
452
466
  Two tiers, plus advisory guards — all designed so legitimate work is never
@@ -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.8"
10
+ version = "3.1.10"
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"
@@ -89,3 +89,73 @@ def test_save_drops_runtime_only_keys(tmp_path):
89
89
  assert "cwd" not in written
90
90
  assert written["context_limit"] == 999
91
91
  assert written["model"] == "g"
92
+
93
+
94
+ # ── custom system prompt (~/.drydock/system_prompt.md or the `system_prompt` key) ──
95
+
96
+ def test_user_system_prompt_empty_by_default(monkeypatch, tmp_path):
97
+ monkeypatch.setattr(cfg.Path, "home", staticmethod(lambda: tmp_path))
98
+ assert cfg.load_user_system_prompt({}) == ""
99
+ assert cfg.load_user_system_prompt({"system_prompt": ""}) == ""
100
+
101
+
102
+ def test_user_system_prompt_inline_key(monkeypatch, tmp_path):
103
+ monkeypatch.setattr(cfg.Path, "home", staticmethod(lambda: tmp_path))
104
+ out = cfg.load_user_system_prompt({"system_prompt": "Always answer in French."})
105
+ assert "Always answer in French." in out
106
+ assert "Custom instructions" in out
107
+
108
+
109
+ def test_user_system_prompt_file_wins_over_inline(monkeypatch, tmp_path):
110
+ d = tmp_path / ".drydock"
111
+ d.mkdir()
112
+ (d / "system_prompt.md").write_text("FILE WINS")
113
+ monkeypatch.setattr(cfg.Path, "home", staticmethod(lambda: tmp_path))
114
+ out = cfg.load_user_system_prompt({"system_prompt": "inline loses"})
115
+ assert "FILE WINS" in out
116
+ assert "inline loses" not in out
117
+
118
+
119
+ def test_user_system_prompt_capped_at_8000(monkeypatch, tmp_path):
120
+ monkeypatch.setattr(cfg.Path, "home", staticmethod(lambda: tmp_path))
121
+ out = cfg.load_user_system_prompt({"system_prompt": "x" * 20000})
122
+ assert out.count("x") == 8000
123
+
124
+
125
+ def test_system_prompt_key_in_defaults():
126
+ assert "system_prompt" in cfg.DEFAULTS
127
+ assert cfg.DEFAULTS["system_prompt"] == ""
128
+
129
+
130
+ def test_resolve_creates_inert_system_prompt_template(tmp_path):
131
+ # resolve() drops the template next to config.toml, and it has NO effect until edited
132
+ cfgpath = tmp_path / "config.toml"
133
+ cfg.resolve({}, cfgpath)
134
+ tmpl = tmp_path / "system_prompt.md"
135
+ assert tmpl.exists(), "template should be auto-created"
136
+ assert "<!--" in tmpl.read_text()
137
+ # inert: all-comments template contributes nothing to the prompt
138
+ assert cfg._strip_comments(tmpl.read_text()).strip() == ""
139
+
140
+
141
+ def test_ensure_system_prompt_never_overwrites(tmp_path):
142
+ f = tmp_path / "system_prompt.md"
143
+ f.write_text("MY REAL PROMPT")
144
+ cfg.ensure_system_prompt_file(tmp_path)
145
+ assert f.read_text() == "MY REAL PROMPT"
146
+
147
+
148
+ def test_load_strips_comments_keeps_real_text(monkeypatch, tmp_path):
149
+ d = tmp_path / ".drydock"; d.mkdir()
150
+ (d / "system_prompt.md").write_text("<!-- ignore me -->\nReal instruction here.")
151
+ monkeypatch.setattr(cfg.Path, "home", staticmethod(lambda: tmp_path))
152
+ out = cfg.load_user_system_prompt({})
153
+ assert "Real instruction here." in out
154
+ assert "ignore me" not in out
155
+
156
+
157
+ def test_load_ignores_comment_only_file(monkeypatch, tmp_path):
158
+ d = tmp_path / ".drydock"; d.mkdir()
159
+ (d / "system_prompt.md").write_text(cfg._SYSTEM_PROMPT_TEMPLATE)
160
+ monkeypatch.setattr(cfg.Path, "home", staticmethod(lambda: tmp_path))
161
+ assert cfg.load_user_system_prompt({}) == ""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes