drydock-cli 3.0.97__tar.gz → 3.0.98__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.
- {drydock_cli-3.0.97/drydock_cli.egg-info → drydock_cli-3.0.98}/PKG-INFO +1 -1
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/tools/__init__.py +7 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98/drydock_cli.egg-info}/PKG-INFO +1 -1
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock_cli.egg-info/SOURCES.txt +1 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/pyproject.toml +1 -1
- drydock_cli-3.0.98/tests/test_write_content_coerce.py +37 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/LICENSE +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/NOTICE +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/README.md +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/__init__.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/advisor.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/agent.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/builtin_skills/__init__.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/builtin_skills/rmf-categorize.md +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/builtin_skills/rmf-control.md +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/builtin_skills/rmf-poam.md +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/builtin_skills/rmf-review.md +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/builtin_skills/stig-assess.md +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/builtin_skills/stig-remediate.md +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/cci.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/cli.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/config.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/detect.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/extract.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/gittools.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/graphrag.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/guards.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/mcp.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/poam.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/providers.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/rmf.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/rmf_graph.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/skills.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/stig.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/tui/app.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock/web.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/setup.cfg +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_advisor.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_bash_background.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_bash_binary_output.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_bash_sanitize.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_bash_shell.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_bash_stdin.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_bash_timeout_param.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_cci.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_config.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_e2e_connected.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_extract.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_gittools.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_graphify_example.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_grep_and_read_robust.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_mcp.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_poam.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_read_index.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_rmf.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_rmf_graph.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_rmf_stig_graph.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_server_probe.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_skills.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_stig.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_system_prompt_help.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_tuning.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_viewimage.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_web_tools.py +0 -0
- {drydock_cli-3.0.97 → drydock_cli-3.0.98}/tests/test_xccdf.py +0 -0
|
@@ -671,6 +671,13 @@ def tool_write(params: dict, config: dict) -> str:
|
|
|
671
671
|
if Path(fp).is_dir():
|
|
672
672
|
return f"Error: {fp} is a directory, not a file. Pass a file path."
|
|
673
673
|
content = params.get("content", "")
|
|
674
|
+
# Local models sometimes send content as a JSON array of lines (or a number),
|
|
675
|
+
# which f.write() can't take — coerce instead of crashing with a TypeError
|
|
676
|
+
# (tools must return errors, never raise). A list → newline-joined lines.
|
|
677
|
+
if isinstance(content, (list, tuple)):
|
|
678
|
+
content = "\n".join(str(x) for x in content)
|
|
679
|
+
elif not isinstance(content, str):
|
|
680
|
+
content = "" if content is None else str(content)
|
|
674
681
|
if has_conflict_markers(content):
|
|
675
682
|
return conflict_marker_refusal(fp)
|
|
676
683
|
prev = _snapshot(fp)
|
|
@@ -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.
|
|
10
|
+
version = "3.0.98"
|
|
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,37 @@
|
|
|
1
|
+
"""tool_write must not raise on non-string content. Local models sometimes send
|
|
2
|
+
`content` as a JSON array of lines (or a number); f.write() would TypeError, and
|
|
3
|
+
tools must return a result, never raise. A list → newline-joined lines."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from drydock.tools import tool_write
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _write(tmp_path, content):
|
|
10
|
+
fp = tmp_path / "out.txt"
|
|
11
|
+
out = tool_write({"file_path": str(fp), "content": content}, {"cwd": str(tmp_path)})
|
|
12
|
+
return out, fp.read_text()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_list_content_joined(tmp_path):
|
|
16
|
+
out, text = _write(tmp_path, ["line1", "line2", "line3"])
|
|
17
|
+
assert "Error" not in out and text == "line1\nline2\nline3"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_int_content_stringified(tmp_path):
|
|
21
|
+
out, text = _write(tmp_path, 42)
|
|
22
|
+
assert "Error" not in out and text == "42"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_none_content_empty(tmp_path):
|
|
26
|
+
out, text = _write(tmp_path, None)
|
|
27
|
+
assert "Error" not in out and text == ""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_normal_string_unchanged(tmp_path):
|
|
31
|
+
out, text = _write(tmp_path, "hello\nworld")
|
|
32
|
+
assert text == "hello\nworld"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_list_of_numbers(tmp_path):
|
|
36
|
+
out, text = _write(tmp_path, [1, 2, 3])
|
|
37
|
+
assert text == "1\n2\n3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|