drydock-cli 3.0.20__tar.gz → 3.0.21__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.20/drydock_cli.egg-info → drydock_cli-3.0.21}/PKG-INFO +1 -1
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/__init__.py +1 -1
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/tools/__init__.py +12 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21/drydock_cli.egg-info}/PKG-INFO +1 -1
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/pyproject.toml +1 -1
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_guards_and_tools.py +13 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/LICENSE +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/NOTICE +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/README.md +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/agent.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/cli.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/config.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/detect.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/guards.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/providers.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/tui/app.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock_cli.egg-info/SOURCES.txt +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/setup.cfg +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_config.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.20 → drydock_cli-3.0.21}/tests/test_tuning.py +0 -0
|
@@ -401,6 +401,18 @@ def tool_edit(params: dict, config: dict) -> str:
|
|
|
401
401
|
if count > 1:
|
|
402
402
|
return f"Error: old_string found {count} times in {fp}. Add more context to make it unique."
|
|
403
403
|
updated = content.replace(old, new, 1)
|
|
404
|
+
if updated == content:
|
|
405
|
+
# No-op edit (old_string == new_string, or the replacement changes
|
|
406
|
+
# nothing). Reporting "Edited" here is a false success that invites
|
|
407
|
+
# the model to re-issue the identical call forever. Tell it plainly
|
|
408
|
+
# that nothing changed and to move on — this is what breaks the
|
|
409
|
+
# same-file edit-thrash loop at its source.
|
|
410
|
+
return (
|
|
411
|
+
f"No change: that edit leaves {fp} byte-for-byte identical "
|
|
412
|
+
f"(old_string and new_string are effectively the same). Nothing "
|
|
413
|
+
f"was written. Do NOT repeat this edit — the file already has "
|
|
414
|
+
f"this content, so move on to your next step."
|
|
415
|
+
)
|
|
404
416
|
Path(fp).write_text(updated, encoding="utf-8")
|
|
405
417
|
_record_undo(config, fp, content)
|
|
406
418
|
result = f"Edited {fp}: replaced {len(old)} chars with {len(new)} chars"
|
|
@@ -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.21"
|
|
11
11
|
description = "Drydock — a local, provider-agnostic terminal coding agent for local LLMs"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.12"
|
|
@@ -263,6 +263,19 @@ def test_tool_edit_fuzzy_applies_unique_near_miss(tmp_path):
|
|
|
263
263
|
assert "compute2" in fp.read_text()
|
|
264
264
|
|
|
265
265
|
|
|
266
|
+
def test_tool_edit_noop_when_old_equals_new(tmp_path):
|
|
267
|
+
# old_string == new_string changes nothing. Reporting "Edited" is a false
|
|
268
|
+
# success that loops a weak model; it must say "No change" and not write.
|
|
269
|
+
fp = tmp_path / "f.py"
|
|
270
|
+
fp.write_text("a = 1\n")
|
|
271
|
+
out = tool_edit(
|
|
272
|
+
{"file_path": str(fp), "old_string": "a = 1", "new_string": "a = 1"}, {}
|
|
273
|
+
)
|
|
274
|
+
assert "No change" in out and "Edited" not in out
|
|
275
|
+
assert "move on" in out.lower()
|
|
276
|
+
assert fp.read_text() == "a = 1\n" # untouched
|
|
277
|
+
|
|
278
|
+
|
|
266
279
|
def test_tool_edit_fuzzy_refuses_indent_mismatch(tmp_path):
|
|
267
280
|
# If the near-miss differs in INDENT, applying the model's new_string would
|
|
268
281
|
# corrupt the file — so it must error (and show the real text) instead.
|
|
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
|