drydock-cli 3.0.117__tar.gz → 3.0.119__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 (128) hide show
  1. {drydock_cli-3.0.117/drydock_cli.egg-info → drydock_cli-3.0.119}/PKG-INFO +1 -1
  2. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/recipes.py +19 -0
  3. drydock_cli-3.0.119/drydock/suggest.py +30 -0
  4. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/tui/app.py +45 -0
  5. {drydock_cli-3.0.117 → drydock_cli-3.0.119/drydock_cli.egg-info}/PKG-INFO +1 -1
  6. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock_cli.egg-info/SOURCES.txt +2 -0
  7. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/pyproject.toml +1 -1
  8. drydock_cli-3.0.119/tests/test_suggest.py +20 -0
  9. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/LICENSE +0 -0
  10. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/NOTICE +0 -0
  11. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/README.md +0 -0
  12. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/__init__.py +0 -0
  13. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/__main__.py +0 -0
  14. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/advisor.py +0 -0
  15. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/agent.py +0 -0
  16. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/bash_safety.py +0 -0
  17. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/builtin_skills/__init__.py +0 -0
  18. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/builtin_skills/rmf-categorize.md +0 -0
  19. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/builtin_skills/rmf-control.md +0 -0
  20. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/builtin_skills/rmf-poam.md +0 -0
  21. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/builtin_skills/rmf-review.md +0 -0
  22. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/builtin_skills/stig-assess.md +0 -0
  23. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/builtin_skills/stig-remediate.md +0 -0
  24. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/cci.py +0 -0
  25. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/cli.py +0 -0
  26. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/compaction.py +0 -0
  27. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/config.py +0 -0
  28. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/detect.py +0 -0
  29. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/extract.py +0 -0
  30. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/gittools.py +0 -0
  31. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/graphrag.py +0 -0
  32. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/guards.py +0 -0
  33. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/loop_detect.py +0 -0
  34. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/mcp.py +0 -0
  35. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/poam.py +0 -0
  36. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/providers.py +0 -0
  37. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/rmf.py +0 -0
  38. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/rmf_graph.py +0 -0
  39. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/skills.py +0 -0
  40. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/stig.py +0 -0
  41. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/tool_registry.py +0 -0
  42. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/tools/__init__.py +0 -0
  43. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/tui/__init__.py +0 -0
  44. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/tui/approval.py +0 -0
  45. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/tui/messages.py +0 -0
  46. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/tui/widgets.py +0 -0
  47. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/tuning.py +0 -0
  48. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock/web.py +0 -0
  49. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock_cli.egg-info/dependency_links.txt +0 -0
  50. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock_cli.egg-info/entry_points.txt +0 -0
  51. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock_cli.egg-info/requires.txt +0 -0
  52. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/drydock_cli.egg-info/top_level.txt +0 -0
  53. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/setup.cfg +0 -0
  54. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_advisor.py +0 -0
  55. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_approval.py +0 -0
  56. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_back_command.py +0 -0
  57. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_bash_background.py +0 -0
  58. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_bash_binary_output.py +0 -0
  59. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_bash_crossplatform.py +0 -0
  60. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_bash_output_bounding.py +0 -0
  61. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_bash_process_group.py +0 -0
  62. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_bash_safety.py +0 -0
  63. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_bash_sanitize.py +0 -0
  64. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_bash_shell.py +0 -0
  65. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_bash_stdin.py +0 -0
  66. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_bash_stop_partial.py +0 -0
  67. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_bash_timeout_network.py +0 -0
  68. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_bash_timeout_param.py +0 -0
  69. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_cci.py +0 -0
  70. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_cli_agents.py +0 -0
  71. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_compact_command.py +0 -0
  72. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_compaction.py +0 -0
  73. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_config.py +0 -0
  74. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_config_migration.py +0 -0
  75. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_context_limit_config.py +0 -0
  76. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_detect.py +0 -0
  77. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_dispatch.py +0 -0
  78. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_e2e_connected.py +0 -0
  79. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_edit_replace_all.py +0 -0
  80. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_empty_response.py +0 -0
  81. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_extract.py +0 -0
  82. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_failure_loop.py +0 -0
  83. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_first_run_setup.py +0 -0
  84. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_gittools.py +0 -0
  85. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_glob_edit_edges.py +0 -0
  86. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_graphify_example.py +0 -0
  87. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_graphrag.py +0 -0
  88. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_graphrag_quoted_path.py +0 -0
  89. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_grep_and_read_robust.py +0 -0
  90. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_guards_and_tools.py +0 -0
  91. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_hallucinated_tools.py +0 -0
  92. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_leaked_tool_call.py +0 -0
  93. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_loop_detect.py +0 -0
  94. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_mcp.py +0 -0
  95. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_oneshot_unreachable.py +0 -0
  96. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_overthink_interrupt.py +0 -0
  97. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_plan_autocontinue.py +0 -0
  98. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_poam.py +0 -0
  99. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_providers_unreachable.py +0 -0
  100. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_read_index.py +0 -0
  101. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_recipes.py +0 -0
  102. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_repetition_interrupt.py +0 -0
  103. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_rmf.py +0 -0
  104. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_rmf_graph.py +0 -0
  105. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_rmf_stig_graph.py +0 -0
  106. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_runaway_repetition.py +0 -0
  107. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_screenshot.py +0 -0
  108. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_server_probe.py +0 -0
  109. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_skills.py +0 -0
  110. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_stall_retry.py +0 -0
  111. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_stig.py +0 -0
  112. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_stop.py +0 -0
  113. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_streaming_newlines.py +0 -0
  114. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_subagent.py +0 -0
  115. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_system_prompt_help.py +0 -0
  116. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_todo.py +0 -0
  117. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_tool_arg_coercion.py +0 -0
  118. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_tool_arg_coercion_more.py +0 -0
  119. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_tool_arg_parsing.py +0 -0
  120. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_tools_undo.py +0 -0
  121. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_tui.py +0 -0
  122. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_tuning.py +0 -0
  123. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_viewimage.py +0 -0
  124. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_vision_input.py +0 -0
  125. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_web_tools.py +0 -0
  126. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_windows_shell.py +0 -0
  127. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_write_content_coerce.py +0 -0
  128. {drydock_cli-3.0.117 → drydock_cli-3.0.119}/tests/test_xccdf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.117
3
+ Version: 3.0.119
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
@@ -245,6 +245,25 @@ RECIPES: list[tuple[str, list[str], str]] = [
245
245
  "Build: pdflatex -interaction=nonstopmode report.tex (run twice for refs).\n"
246
246
  "Escape %, _, &, # in text. pandas: df.to_latex('t.tex', index=False). Confirm\n"
247
247
  "report.pdf exists and pdflatex exit code is 0."),
248
+
249
+ ("Reinforcement learning — Q-learning / policy on a small environment",
250
+ ["rl", "reinforcement", "q-learning", "qlearning", "policy", "reward", "agent",
251
+ "environment", "gridworld", "episode", "bellman", "value", "action", "dqn",
252
+ "exploration", "epsilon", "gym", "cartpole", "bandit"],
253
+ "Tabular Q-learning for a discrete env with reset()/step(a)->(s2,r,done):\n"
254
+ " Q = np.zeros((nS, nA))\n"
255
+ " for ep in range(N):\n"
256
+ " s = env.reset()\n"
257
+ " for t in range(max_steps):\n"
258
+ " a = randint(nA) if rand() < eps else int(Q[s].argmax()) # eps-greedy\n"
259
+ " s2, r, done = env.step(a)\n"
260
+ " Q[s,a] += alpha * (r + gamma * Q[s2].max() - Q[s,a]) # Bellman update\n"
261
+ " s = s2\n"
262
+ " if done: break\n"
263
+ "Greedy policy = Q.argmax(1). Typical alpha=0.5, gamma=0.95, eps=0.1-0.2 (decay it).\n"
264
+ "Sanity: average episode reward should rise and the greedy policy should reach the\n"
265
+ "goal. For large/continuous states use a neural Q (DQN: replay buffer + target net)\n"
266
+ "or a policy network (REINFORCE: maximize sum of log_prob(a)*return)."),
248
267
  ]
249
268
 
250
269
  _WORD = re.compile(r"[a-z0-9.]+")
@@ -0,0 +1,30 @@
1
+ """Recommended next command — a single dimmed hint shown after each turn (à la
2
+ Claude Code's ghost suggestion). Pure logic so it's testable; the TUI renders it.
3
+ """
4
+ from __future__ import annotations
5
+
6
+
7
+ def suggest_next_command(
8
+ *,
9
+ ctx_pct: int,
10
+ wrote_files: bool,
11
+ ran_bash: bool,
12
+ had_error: bool,
13
+ in_git: bool,
14
+ plan_remaining: bool,
15
+ ) -> str | None:
16
+ """The single most useful next step given what the last turn did, or None to
17
+ show nothing (better no hint than a noisy one). Order = priority."""
18
+ if ctx_pct >= 78:
19
+ return "/compact" # context nearly full — shrink it before continuing
20
+ if had_error:
21
+ return "fix the error above"
22
+ if plan_remaining:
23
+ return "continue" # the model left plan steps unfinished
24
+ if wrote_files and in_git:
25
+ return "git diff" # review what changed
26
+ if wrote_files:
27
+ return "run the tests"
28
+ if ran_bash:
29
+ return None # ran a command but nothing else obvious — no clutter
30
+ return None
@@ -111,6 +111,8 @@ class DrydockApp(App):
111
111
  height: auto; margin: 0 2 1 2; padding: 0 1; color: #d7e6ee;
112
112
  border-left: thick #c9a227; background: #14241c;
113
113
  }
114
+ /* Dimmed recommended-next-command hint (empty → 0 lines). */
115
+ #suggest { height: auto; margin: 0 3; color: #4a6b78; text-style: italic; }
114
116
  /* One bottom-docked footer holds, top→bottom: working-line, prompt, status. */
115
117
  #footer { dock: bottom; height: auto; background: #0b1f2a; }
116
118
  #prompt {
@@ -140,6 +142,7 @@ class DrydockApp(App):
140
142
  # does the same. priority so it isn't swallowed by the focused prompt.
141
143
  Binding("escape", "stop", "Stop the running turn", priority=True),
142
144
  Binding("ctrl+o", "toggle_tools", "Expand/collapse details"),
145
+ Binding("ctrl+n", "use_suggestion", "Use suggested next command", show=False),
143
146
  # Scroll the transcript from the keyboard (focus stays on the prompt,
144
147
  # and SSH sessions often don't forward the mouse wheel). priority=True
145
148
  # so the prompt's TextArea doesn't swallow PageUp/PageDown first.
@@ -250,6 +253,13 @@ class DrydockApp(App):
250
253
  self._skills = load_skills(config.get("cwd") or ".")
251
254
  self._current_assistant: AssistantMessage | None = None
252
255
  self._last_card: ToolCard | None = None
256
+ # Recommended-next-command hint state.
257
+ self._turn_tools: set[str] = set() # tool names used this turn
258
+ self._turn_error = False
259
+ self._plan_remaining = False
260
+ self._suggestion = ""
261
+ import os
262
+ self._in_git = os.path.isdir(os.path.join(config.get("cwd") or ".", ".git"))
253
263
  self._busy = False
254
264
  # STOP signal: Escape / "/stop" sets it; the agent loop checks it at safe
255
265
  # points and ends the turn cleanly (session preserved). Lives in config
@@ -300,6 +310,7 @@ class DrydockApp(App):
300
310
  with Vertical(id="footer"):
301
311
  yield Static("", id="todo") # pinned task checklist (empty = hidden)
302
312
  yield Static("", id="working") # in-line activity (empty when idle)
313
+ yield Static("", id="suggest") # dimmed recommended-next-command hint
303
314
  yield PromptArea(id="prompt")
304
315
  yield Static(self._status_text(), id="status")
305
316
 
@@ -446,6 +457,9 @@ class DrydockApp(App):
446
457
  # turn. If this turn emits its own todo, _render_todo repopulates it.
447
458
  self.config.pop("_todo", None)
448
459
  self.query_one("#todo", Static).update("")
460
+ self._turn_tools = set()
461
+ self._turn_error = False
462
+ self.query_one("#suggest", Static).update("") # hide the hint while working
449
463
  self._busy = True
450
464
  self._work_start = time.monotonic()
451
465
  self._last_progress = self._work_start
@@ -1210,6 +1224,7 @@ class DrydockApp(App):
1210
1224
 
1211
1225
  def on_agent_tool_start(self, m: AgentToolStart) -> None:
1212
1226
  self._current_assistant = None # end the current text block
1227
+ self._turn_tools.add(m.name)
1213
1228
  if m.name == "todo":
1214
1229
  self._render_todo(m.inputs.get("tasks", ""))
1215
1230
  self._last_card = None # tool_end is a no-op for the checklist
@@ -1229,6 +1244,7 @@ class DrydockApp(App):
1229
1244
 
1230
1245
  items = parse_todo(tasks)
1231
1246
  panel = self.query_one("#todo", Static)
1247
+ self._plan_remaining = any(s != "done" for _, s in items)
1232
1248
  if not items:
1233
1249
  panel.update("") # collapses to 0 height
1234
1250
  return
@@ -1269,9 +1285,38 @@ class DrydockApp(App):
1269
1285
  return
1270
1286
  self._repeat = None
1271
1287
  self._refresh_status()
1288
+ self._update_suggestion()
1272
1289
  self.query_one("#prompt", PromptArea).focus()
1273
1290
 
1291
+ def _update_suggestion(self) -> None:
1292
+ """Compute + render the dimmed recommended-next-command hint (Claude-Code-
1293
+ style). Empty when nothing useful to suggest."""
1294
+ from drydock.suggest import suggest_next_command
1295
+ limit = self.config.get("context_limit", 65536) or 65536
1296
+ pct = min(100, round(self._ctx_tokens / limit * 100)) if limit else 0
1297
+ wrote = bool(self._turn_tools & {"Write", "Edit"})
1298
+ ran = "Bash" in self._turn_tools
1299
+ self._suggestion = suggest_next_command(
1300
+ ctx_pct=pct, wrote_files=wrote, ran_bash=ran, had_error=self._turn_error,
1301
+ in_git=self._in_git, plan_remaining=self._plan_remaining,
1302
+ ) or ""
1303
+ hint = self.query_one("#suggest", Static)
1304
+ if self._suggestion:
1305
+ from rich.markup import escape
1306
+ hint.update(f"→ next: {escape(self._suggestion)} [dim](ctrl+n to use)[/]")
1307
+ else:
1308
+ hint.update("")
1309
+
1310
+ def action_use_suggestion(self) -> None:
1311
+ """Accept the recommended next command — drop it into the prompt to edit/send."""
1312
+ if self._suggestion and not self._busy:
1313
+ prompt = self.query_one("#prompt", PromptArea)
1314
+ prompt.text = self._suggestion
1315
+ prompt.move_cursor(prompt.document.end)
1316
+ prompt.focus()
1317
+
1274
1318
  def on_agent_error(self, m: AgentError) -> None:
1319
+ self._turn_error = True
1275
1320
  self._mount(ErrorMessage(m.error))
1276
1321
 
1277
1322
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.117
3
+ Version: 3.0.119
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
@@ -25,6 +25,7 @@ drydock/rmf.py
25
25
  drydock/rmf_graph.py
26
26
  drydock/skills.py
27
27
  drydock/stig.py
28
+ drydock/suggest.py
28
29
  drydock/tool_registry.py
29
30
  drydock/tuning.py
30
31
  drydock/web.py
@@ -108,6 +109,7 @@ tests/test_stig.py
108
109
  tests/test_stop.py
109
110
  tests/test_streaming_newlines.py
110
111
  tests/test_subagent.py
112
+ tests/test_suggest.py
111
113
  tests/test_system_prompt_help.py
112
114
  tests/test_todo.py
113
115
  tests/test_tool_arg_coercion.py
@@ -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.0.117"
10
+ version = "3.0.119"
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"
@@ -0,0 +1,20 @@
1
+ """Recommended-next-command suggestion logic (priority-ordered, None when noisy)."""
2
+ from drydock.suggest import suggest_next_command as s
3
+
4
+
5
+ def test_priority_and_cases():
6
+ base = dict(ctx_pct=10, wrote_files=False, ran_bash=False, had_error=False,
7
+ in_git=False, plan_remaining=False)
8
+ assert s(**{**base, "ctx_pct": 80}) == "/compact" # ctx wins
9
+ assert s(**{**base, "ctx_pct": 80, "had_error": True}) == "/compact"
10
+ assert s(**{**base, "had_error": True}) == "fix the error above"
11
+ assert s(**{**base, "plan_remaining": True}) == "continue"
12
+ assert s(**{**base, "wrote_files": True, "in_git": True}) == "git diff"
13
+ assert s(**{**base, "wrote_files": True}) == "run the tests"
14
+ assert s(**{**base, "ran_bash": True}) is None # no clutter
15
+ assert s(**base) is None
16
+
17
+
18
+ def test_error_beats_plan_and_writes():
19
+ assert s(ctx_pct=10, wrote_files=True, ran_bash=True, had_error=True,
20
+ in_git=True, plan_remaining=True) == "fix the error above"
File without changes
File without changes
File without changes
File without changes