drydock-cli 3.0.104__tar.gz → 3.0.105__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 (118) hide show
  1. {drydock_cli-3.0.104/drydock_cli.egg-info → drydock_cli-3.0.105}/PKG-INFO +1 -1
  2. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/tools/__init__.py +38 -5
  3. {drydock_cli-3.0.104 → drydock_cli-3.0.105/drydock_cli.egg-info}/PKG-INFO +1 -1
  4. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock_cli.egg-info/SOURCES.txt +1 -0
  5. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/pyproject.toml +1 -1
  6. drydock_cli-3.0.105/tests/test_bash_crossplatform.py +49 -0
  7. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/LICENSE +0 -0
  8. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/NOTICE +0 -0
  9. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/README.md +0 -0
  10. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/__init__.py +0 -0
  11. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/__main__.py +0 -0
  12. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/advisor.py +0 -0
  13. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/agent.py +0 -0
  14. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/bash_safety.py +0 -0
  15. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/builtin_skills/__init__.py +0 -0
  16. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/builtin_skills/rmf-categorize.md +0 -0
  17. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/builtin_skills/rmf-control.md +0 -0
  18. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/builtin_skills/rmf-poam.md +0 -0
  19. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/builtin_skills/rmf-review.md +0 -0
  20. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/builtin_skills/stig-assess.md +0 -0
  21. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/builtin_skills/stig-remediate.md +0 -0
  22. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/cci.py +0 -0
  23. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/cli.py +0 -0
  24. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/compaction.py +0 -0
  25. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/config.py +0 -0
  26. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/detect.py +0 -0
  27. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/extract.py +0 -0
  28. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/gittools.py +0 -0
  29. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/graphrag.py +0 -0
  30. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/guards.py +0 -0
  31. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/loop_detect.py +0 -0
  32. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/mcp.py +0 -0
  33. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/poam.py +0 -0
  34. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/providers.py +0 -0
  35. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/rmf.py +0 -0
  36. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/rmf_graph.py +0 -0
  37. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/skills.py +0 -0
  38. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/stig.py +0 -0
  39. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/tool_registry.py +0 -0
  40. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/tui/__init__.py +0 -0
  41. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/tui/app.py +0 -0
  42. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/tui/approval.py +0 -0
  43. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/tui/messages.py +0 -0
  44. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/tui/widgets.py +0 -0
  45. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/tuning.py +0 -0
  46. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock/web.py +0 -0
  47. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock_cli.egg-info/dependency_links.txt +0 -0
  48. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock_cli.egg-info/entry_points.txt +0 -0
  49. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock_cli.egg-info/requires.txt +0 -0
  50. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/drydock_cli.egg-info/top_level.txt +0 -0
  51. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/setup.cfg +0 -0
  52. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_advisor.py +0 -0
  53. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_approval.py +0 -0
  54. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_back_command.py +0 -0
  55. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_bash_background.py +0 -0
  56. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_bash_binary_output.py +0 -0
  57. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_bash_output_bounding.py +0 -0
  58. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_bash_process_group.py +0 -0
  59. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_bash_safety.py +0 -0
  60. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_bash_sanitize.py +0 -0
  61. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_bash_shell.py +0 -0
  62. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_bash_stdin.py +0 -0
  63. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_bash_stop_partial.py +0 -0
  64. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_bash_timeout_network.py +0 -0
  65. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_bash_timeout_param.py +0 -0
  66. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_cci.py +0 -0
  67. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_cli_agents.py +0 -0
  68. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_compact_command.py +0 -0
  69. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_compaction.py +0 -0
  70. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_config.py +0 -0
  71. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_config_migration.py +0 -0
  72. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_context_limit_config.py +0 -0
  73. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_detect.py +0 -0
  74. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_dispatch.py +0 -0
  75. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_e2e_connected.py +0 -0
  76. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_edit_replace_all.py +0 -0
  77. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_empty_response.py +0 -0
  78. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_extract.py +0 -0
  79. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_failure_loop.py +0 -0
  80. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_first_run_setup.py +0 -0
  81. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_gittools.py +0 -0
  82. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_glob_edit_edges.py +0 -0
  83. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_graphify_example.py +0 -0
  84. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_graphrag.py +0 -0
  85. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_grep_and_read_robust.py +0 -0
  86. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_guards_and_tools.py +0 -0
  87. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_hallucinated_tools.py +0 -0
  88. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_leaked_tool_call.py +0 -0
  89. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_loop_detect.py +0 -0
  90. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_mcp.py +0 -0
  91. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_oneshot_unreachable.py +0 -0
  92. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_plan_autocontinue.py +0 -0
  93. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_poam.py +0 -0
  94. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_providers_unreachable.py +0 -0
  95. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_read_index.py +0 -0
  96. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_rmf.py +0 -0
  97. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_rmf_graph.py +0 -0
  98. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_rmf_stig_graph.py +0 -0
  99. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_runaway_repetition.py +0 -0
  100. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_server_probe.py +0 -0
  101. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_skills.py +0 -0
  102. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_stig.py +0 -0
  103. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_stop.py +0 -0
  104. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_streaming_newlines.py +0 -0
  105. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_subagent.py +0 -0
  106. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_system_prompt_help.py +0 -0
  107. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_todo.py +0 -0
  108. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_tool_arg_coercion.py +0 -0
  109. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_tool_arg_coercion_more.py +0 -0
  110. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_tool_arg_parsing.py +0 -0
  111. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_tools_undo.py +0 -0
  112. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_tui.py +0 -0
  113. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_tuning.py +0 -0
  114. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_viewimage.py +0 -0
  115. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_vision_input.py +0 -0
  116. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_web_tools.py +0 -0
  117. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/tests/test_write_content_coerce.py +0 -0
  118. {drydock_cli-3.0.104 → drydock_cli-3.0.105}/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.104
3
+ Version: 3.0.105
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
@@ -82,14 +82,20 @@ def _detect_bash() -> str | None:
82
82
  found = shutil.which("bash")
83
83
  if found:
84
84
  return found
85
- for p in ("/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash"):
85
+ # Common absolute locations, incl. Git-for-Windows and WSL bash on Windows.
86
+ for p in ("/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash",
87
+ r"C:\Program Files\Git\bin\bash.exe",
88
+ r"C:\Program Files (x86)\Git\bin\bash.exe",
89
+ r"C:\Windows\System32\bash.exe"):
86
90
  if os.path.exists(p):
87
91
  return p
88
92
  return None
89
93
 
90
94
 
91
- # Resolved once at import (in whatever environment drydock runs — host or the
92
- # task container). None bash unavailable, fall back to the default shell.
95
+ _IS_WINDOWS = os.name == "nt"
96
+ # Resolved once at import (in whatever environment drydock runs — host, the task
97
+ # container, Linux, or Windows). None → bash unavailable, fall back to the
98
+ # platform's default shell (cmd.exe on Windows, /bin/sh on POSIX).
93
99
  _BASH_SHELL = _detect_bash()
94
100
 
95
101
 
@@ -928,6 +934,18 @@ def kill_process_group(proc) -> None:
928
934
  if the group can't be resolved (e.g. process already gone)."""
929
935
  if proc is None:
930
936
  return
937
+ if _IS_WINDOWS:
938
+ # No process groups / SIGKILL on Windows — taskkill /T kills the whole
939
+ # child tree (the shell + everything it spawned).
940
+ try:
941
+ subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)],
942
+ capture_output=True, timeout=10)
943
+ except Exception: # noqa: BLE001
944
+ try:
945
+ proc.kill()
946
+ except Exception: # noqa: BLE001
947
+ pass
948
+ return
931
949
  try:
932
950
  os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
933
951
  except (ProcessLookupError, PermissionError, OSError):
@@ -1016,9 +1034,19 @@ def tool_bash(params: dict, config: dict) -> str:
1016
1034
  # overall timeout between slices.
1017
1035
  cancel = config.get("_cancel")
1018
1036
  proc = None
1037
+ # Invoke bash explicitly as [bash, "-c", cmd] rather than shell=True +
1038
+ # executable — cross-platform correctness: on Windows shell=True builds
1039
+ # `{executable} /c {cmd}` (cmd.exe syntax), so a real bash.exe (Git Bash/WSL)
1040
+ # would be called as `bash /c ...` and fail. `[bash, "-c", cmd]` runs the same
1041
+ # on Linux and Windows. Without bash, fall back to the platform default shell
1042
+ # (cmd.exe on Windows, /bin/sh on POSIX) via shell=True.
1043
+ if _BASH_SHELL:
1044
+ popen_cmd, use_shell = [_BASH_SHELL, "-c", cmd], False
1045
+ else:
1046
+ popen_cmd, use_shell = cmd, True
1019
1047
  try:
1020
1048
  proc = subprocess.Popen(
1021
- cmd, shell=True, executable=_BASH_SHELL,
1049
+ popen_cmd, shell=use_shell,
1022
1050
  stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
1023
1051
  # stdin=DEVNULL so a command that reads stdin gets immediate EOF
1024
1052
  # (correct for a non-interactive tool) instead of inheriting the
@@ -1033,7 +1061,12 @@ def tool_bash(params: dict, config: dict) -> str:
1033
1061
  # which killed the thread and handed the agent "(no output)", losing
1034
1062
  # even the text parts of mixed output.
1035
1063
  text=True, encoding="utf-8", errors="replace",
1036
- cwd=config.get("cwd"), start_new_session=True,
1064
+ cwd=config.get("cwd"),
1065
+ # Own process group / job so kill_process_group takes down the whole
1066
+ # tree: setsid on POSIX; a new process group on Windows (getattr keeps
1067
+ # the Windows-only flag out of the POSIX code path — it resolves to 0).
1068
+ start_new_session=not _IS_WINDOWS,
1069
+ creationflags=getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0),
1037
1070
  )
1038
1071
  config.setdefault("_abort", {})["proc"] = proc
1039
1072
  # Read output in a daemon thread with a HARD byte cap, so memory can't
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.104
3
+ Version: 3.0.105
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
@@ -51,6 +51,7 @@ tests/test_approval.py
51
51
  tests/test_back_command.py
52
52
  tests/test_bash_background.py
53
53
  tests/test_bash_binary_output.py
54
+ tests/test_bash_crossplatform.py
54
55
  tests/test_bash_output_bounding.py
55
56
  tests/test_bash_process_group.py
56
57
  tests/test_bash_safety.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.104"
10
+ version = "3.0.105"
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,49 @@
1
+ """tool_bash must run on Linux AND Windows. The Windows blocker was shell=True +
2
+ executable=bash → Popen builds `bash /c cmd` (cmd.exe syntax) which a real
3
+ bash.exe rejects. Fix: invoke `[bash, "-c", cmd]` explicitly; use taskkill on
4
+ Windows for the process-tree kill; setsid only on POSIX."""
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ import subprocess
9
+
10
+ from drydock import tools
11
+ from drydock.tools import _detect_bash, _IS_WINDOWS, kill_process_group, tool_bash
12
+
13
+
14
+ def test_platform_flag_matches_os():
15
+ assert _IS_WINDOWS == (os.name == "nt")
16
+
17
+
18
+ def test_detect_bash_returns_path_or_none():
19
+ r = _detect_bash()
20
+ assert r is None or (isinstance(r, str) and "bash" in r.lower())
21
+
22
+
23
+ def test_bash_invoked_as_argv_not_shell_c(monkeypatch):
24
+ """When bash is available, Popen is called with [bash, '-c', cmd] and
25
+ shell=False — never shell=True+executable (which breaks on Windows)."""
26
+ if not tools._BASH_SHELL:
27
+ return
28
+ seen = {}
29
+ real = subprocess.Popen
30
+
31
+ def spy(popen_cmd, *a, **kw):
32
+ seen["cmd"] = popen_cmd
33
+ seen["shell"] = kw.get("shell")
34
+ return real(popen_cmd, *a, **kw)
35
+
36
+ monkeypatch.setattr(subprocess, "Popen", spy)
37
+ tool_bash({"command": "echo hi", "timeout": 5}, {"cwd": "/tmp"})
38
+ assert seen["shell"] is False
39
+ assert isinstance(seen["cmd"], list) and seen["cmd"][1] == "-c"
40
+ assert seen["cmd"][0].lower().endswith(("bash", "bash.exe"))
41
+
42
+
43
+ def test_kill_process_group_none_is_safe():
44
+ kill_process_group(None) # must not raise
45
+
46
+
47
+ def test_command_still_runs_here():
48
+ out = tool_bash({"command": "echo cross_platform_ok", "timeout": 5}, {"cwd": "/tmp"})
49
+ assert "cross_platform_ok" in out
File without changes
File without changes
File without changes
File without changes