drydock-cli 3.0.96__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.96/drydock_cli.egg-info → drydock_cli-3.0.98}/PKG-INFO +1 -1
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/tools/__init__.py +20 -3
- {drydock_cli-3.0.96 → drydock_cli-3.0.98/drydock_cli.egg-info}/PKG-INFO +1 -1
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock_cli.egg-info/SOURCES.txt +2 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/pyproject.toml +1 -1
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_bash_timeout_param.py +0 -1
- drydock_cli-3.0.98/tests/test_grep_and_read_robust.py +43 -0
- drydock_cli-3.0.98/tests/test_write_content_coerce.py +37 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/LICENSE +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/NOTICE +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/README.md +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/__init__.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/advisor.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/agent.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/builtin_skills/__init__.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/builtin_skills/rmf-categorize.md +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/builtin_skills/rmf-control.md +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/builtin_skills/rmf-poam.md +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/builtin_skills/rmf-review.md +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/builtin_skills/stig-assess.md +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/builtin_skills/stig-remediate.md +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/cci.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/cli.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/config.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/detect.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/extract.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/gittools.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/graphrag.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/guards.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/mcp.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/poam.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/providers.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/rmf.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/rmf_graph.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/skills.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/stig.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/tui/app.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock/web.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/setup.cfg +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_advisor.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_bash_background.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_bash_binary_output.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_bash_sanitize.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_bash_shell.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_bash_stdin.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_cci.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_config.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_e2e_connected.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_extract.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_gittools.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_graphify_example.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_mcp.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_poam.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_read_index.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_rmf.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_rmf_graph.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_rmf_stig_graph.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_server_probe.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_skills.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_stig.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_system_prompt_help.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_tuning.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_viewimage.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_web_tools.py +0 -0
- {drydock_cli-3.0.96 → drydock_cli-3.0.98}/tests/test_xccdf.py +0 -0
|
@@ -601,7 +601,10 @@ def tool_read(params: dict, config: dict) -> str:
|
|
|
601
601
|
eff_limit = 2000 if limit is None else limit
|
|
602
602
|
selected = lines[offset:offset + eff_limit]
|
|
603
603
|
numbered = [f"{i + offset + 1}\t{line.rstrip()}" for i, line in enumerate(selected)]
|
|
604
|
-
|
|
604
|
+
# Drop NUL bytes (valid UTF-8, so errors='replace' keeps them) — raw NUL in
|
|
605
|
+
# a JSON tool result trips some LLM servers. ANSI is left as-is: unlike
|
|
606
|
+
# command output, a file's bytes are content the model may need verbatim.
|
|
607
|
+
result = "\n".join(numbered).replace("\x00", "")
|
|
605
608
|
if len(lines) > offset + eff_limit:
|
|
606
609
|
result += f"\n[... {len(lines) - offset - eff_limit} more lines]"
|
|
607
610
|
return result or "(empty file)"
|
|
@@ -668,6 +671,13 @@ def tool_write(params: dict, config: dict) -> str:
|
|
|
668
671
|
if Path(fp).is_dir():
|
|
669
672
|
return f"Error: {fp} is a directory, not a file. Pass a file path."
|
|
670
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)
|
|
671
681
|
if has_conflict_markers(content):
|
|
672
682
|
return conflict_marker_refusal(fp)
|
|
673
683
|
prev = _snapshot(fp)
|
|
@@ -1104,9 +1114,16 @@ def tool_grep(params: dict, config: dict) -> str:
|
|
|
1104
1114
|
cmd.extend(["--include", include])
|
|
1105
1115
|
cmd.extend([pattern, path])
|
|
1106
1116
|
result = subprocess.run(
|
|
1107
|
-
cmd, capture_output=True, text=True,
|
|
1117
|
+
cmd, capture_output=True, text=True, encoding="utf-8",
|
|
1118
|
+
errors="replace", timeout=30,
|
|
1108
1119
|
)
|
|
1109
|
-
|
|
1120
|
+
# grep exit codes: 0 = matched, 1 = no match, >=2 = ERROR (invalid regex,
|
|
1121
|
+
# unreadable path). Don't report an error as "(no matches)" — the model
|
|
1122
|
+
# would wrongly conclude the pattern is absent instead of fixing it.
|
|
1123
|
+
if result.returncode >= 2:
|
|
1124
|
+
err = (result.stderr or "").strip() or "grep failed"
|
|
1125
|
+
return f"Error: {err.splitlines()[0]}"
|
|
1126
|
+
output = _sanitize_bash_output(result.stdout).strip()
|
|
1110
1127
|
if not output:
|
|
1111
1128
|
return "(no matches)"
|
|
1112
1129
|
lines = output.split("\n")
|
|
@@ -76,6 +76,7 @@ tests/test_first_run_setup.py
|
|
|
76
76
|
tests/test_gittools.py
|
|
77
77
|
tests/test_graphify_example.py
|
|
78
78
|
tests/test_graphrag.py
|
|
79
|
+
tests/test_grep_and_read_robust.py
|
|
79
80
|
tests/test_guards_and_tools.py
|
|
80
81
|
tests/test_hallucinated_tools.py
|
|
81
82
|
tests/test_leaked_tool_call.py
|
|
@@ -105,4 +106,5 @@ tests/test_tuning.py
|
|
|
105
106
|
tests/test_viewimage.py
|
|
106
107
|
tests/test_vision_input.py
|
|
107
108
|
tests/test_web_tools.py
|
|
109
|
+
tests/test_write_content_coerce.py
|
|
108
110
|
tests/test_xccdf.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.
|
|
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,43 @@
|
|
|
1
|
+
"""tool_grep must not report a grep ERROR (invalid regex, unreadable path — grep
|
|
2
|
+
exit >=2) as "(no matches)"; and both grep and Read must be binary-safe (no crash,
|
|
3
|
+
no raw NUL). Found by probing the grep/read I/O paths."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from drydock.tools import tool_grep, tool_read
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _grep(pattern, path):
|
|
10
|
+
return tool_grep({"pattern": pattern, "path": path}, {"cwd": "/tmp"})
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_grep_valid_match(tmp_path):
|
|
14
|
+
f = tmp_path / "a.txt"; f.write_text("hello world\nfoo\n")
|
|
15
|
+
assert "hello world" in _grep("hello", str(f))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_grep_genuine_no_match(tmp_path):
|
|
19
|
+
f = tmp_path / "a.txt"; f.write_text("hello\n")
|
|
20
|
+
assert _grep("zzznope", str(f)).strip() == "(no matches)"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_grep_invalid_regex_is_error_not_no_matches(tmp_path):
|
|
24
|
+
f = tmp_path / "a.txt"; f.write_text("hello\n")
|
|
25
|
+
out = _grep("[unclosed", str(f))
|
|
26
|
+
assert out.startswith("Error:") and "no matches" not in out
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_grep_missing_path_is_error(tmp_path):
|
|
30
|
+
out = _grep("x", str(tmp_path / "nope_xyz"))
|
|
31
|
+
assert out.startswith("Error:")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_grep_binary_does_not_crash(tmp_path):
|
|
35
|
+
f = tmp_path / "b.bin"; f.write_bytes(b"\xff\xfe match \x00\xc0\n")
|
|
36
|
+
out = _grep("match", str(f))
|
|
37
|
+
assert "Error:" not in out or "no matches" in out or "match" in out # no exception
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_read_drops_nul_bytes(tmp_path):
|
|
41
|
+
f = tmp_path / "n.txt"; f.write_bytes(b"before\x00\x00after text\n")
|
|
42
|
+
out = tool_read({"file_path": str(f)}, {"cwd": str(tmp_path)})
|
|
43
|
+
assert "\x00" not in out and "after text" in out
|
|
@@ -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
|