ciayn fixes
This commit is contained in:
parent
e45cd78c7f
commit
bd02bffc55
|
|
@ -74,14 +74,15 @@ def get_file_listing(
|
||||||
directory: str, limit: Optional[int] = None, include_hidden: bool = False
|
directory: str, limit: Optional[int] = None, include_hidden: bool = False
|
||||||
) -> Tuple[List[str], int]:
|
) -> Tuple[List[str], int]:
|
||||||
"""
|
"""
|
||||||
Get a list of tracked files in a git repository.
|
Get a list of files in a directory.
|
||||||
|
|
||||||
Uses `git ls-files` for efficient file listing that respects .gitignore rules.
|
For git repositories, uses `git ls-files` for efficient file listing that respects .gitignore rules.
|
||||||
|
For non-git directories, falls back to manual file listing using Python's standard library.
|
||||||
Returns a tuple containing the list of files (truncated if limit is specified)
|
Returns a tuple containing the list of files (truncated if limit is specified)
|
||||||
and the total count of files.
|
and the total count of files.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
directory: Path to the git repository
|
directory: Path to the directory
|
||||||
limit: Optional maximum number of files to return
|
limit: Optional maximum number of files to return
|
||||||
include_hidden: Whether to include hidden files (starting with .) in the results
|
include_hidden: Whether to include hidden files (starting with .) in the results
|
||||||
|
|
||||||
|
|
@ -104,50 +105,74 @@ def get_file_listing(
|
||||||
raise DirectoryNotFoundError(f"Not a directory: {directory}")
|
raise DirectoryNotFoundError(f"Not a directory: {directory}")
|
||||||
|
|
||||||
# Check if it's a git repository
|
# Check if it's a git repository
|
||||||
if not is_git_repo(directory):
|
is_git = is_git_repo(directory)
|
||||||
return [], 0
|
|
||||||
|
|
||||||
# Get list of files from git ls-files
|
|
||||||
try:
|
|
||||||
# Get both tracked and untracked files
|
|
||||||
tracked_files_process = subprocess.run(
|
|
||||||
["git", "ls-files"],
|
|
||||||
cwd=directory,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
untracked_files_process = subprocess.run(
|
|
||||||
["git", "ls-files", "--others", "--exclude-standard"],
|
|
||||||
cwd=directory,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
raise GitCommandError(f"Git command failed: {e}")
|
|
||||||
except PermissionError as e:
|
|
||||||
raise DirectoryAccessError(f"Permission denied: {e}")
|
|
||||||
|
|
||||||
# Combine and process the files
|
|
||||||
all_files = []
|
all_files = []
|
||||||
for file in (
|
|
||||||
tracked_files_process.stdout.splitlines()
|
if is_git:
|
||||||
+ untracked_files_process.stdout.splitlines()
|
# Get list of files from git ls-files
|
||||||
):
|
try:
|
||||||
file = file.strip()
|
# Get both tracked and untracked files
|
||||||
if not file:
|
tracked_files_process = subprocess.run(
|
||||||
continue
|
["git", "ls-files"],
|
||||||
# Skip hidden files unless explicitly included
|
cwd=directory,
|
||||||
if not include_hidden and (
|
capture_output=True,
|
||||||
file.startswith(".")
|
text=True,
|
||||||
or any(part.startswith(".") for part in file.split("/"))
|
check=True,
|
||||||
|
)
|
||||||
|
untracked_files_process = subprocess.run(
|
||||||
|
["git", "ls-files", "--others", "--exclude-standard"],
|
||||||
|
cwd=directory,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
raise GitCommandError(f"Git command failed: {e}")
|
||||||
|
except PermissionError as e:
|
||||||
|
raise DirectoryAccessError(f"Permission denied: {e}")
|
||||||
|
|
||||||
|
# Combine and process the files
|
||||||
|
for file in (
|
||||||
|
tracked_files_process.stdout.splitlines()
|
||||||
|
+ untracked_files_process.stdout.splitlines()
|
||||||
):
|
):
|
||||||
continue
|
file = file.strip()
|
||||||
# Skip .aider files
|
if not file:
|
||||||
if ".aider" in file:
|
continue
|
||||||
continue
|
# Skip hidden files unless explicitly included
|
||||||
all_files.append(file)
|
if not include_hidden and (
|
||||||
|
file.startswith(".")
|
||||||
|
or any(part.startswith(".") for part in file.split("/"))
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
# Skip .aider files
|
||||||
|
if ".aider" in file:
|
||||||
|
continue
|
||||||
|
all_files.append(file)
|
||||||
|
else:
|
||||||
|
# Not a git repository, use manual file listing
|
||||||
|
base_path = Path(directory)
|
||||||
|
excluded_dirs = {'.ra-aid', '.venv', '.git', '.aider', '__pycache__'}
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(directory):
|
||||||
|
# Filter out excluded directories
|
||||||
|
dirs[:] = [d for d in dirs if d not in excluded_dirs and (include_hidden or not d.startswith('.'))]
|
||||||
|
|
||||||
|
# Calculate relative path
|
||||||
|
rel_root = os.path.relpath(root, directory)
|
||||||
|
if rel_root == '.':
|
||||||
|
rel_root = ''
|
||||||
|
|
||||||
|
# Process files
|
||||||
|
for file in files:
|
||||||
|
# Skip hidden files unless explicitly included
|
||||||
|
if not include_hidden and file.startswith('.'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Create relative path
|
||||||
|
rel_path = os.path.join(rel_root, file) if rel_root else file
|
||||||
|
all_files.append(rel_path)
|
||||||
|
|
||||||
# Remove duplicates and sort
|
# Remove duplicates and sort
|
||||||
all_files = sorted(set(all_files))
|
all_files = sorted(set(all_files))
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ def is_new_project(directory: str) -> bool:
|
||||||
|
|
||||||
A project is considered new if it either:
|
A project is considered new if it either:
|
||||||
- Is an empty directory
|
- Is an empty directory
|
||||||
- Contains only .git directory, .gitignore file, and/or .ra-aid directory
|
- Contains only .git directory, .gitignore file, .venv directory, and/or .ra-aid directory
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
directory: String path to the directory to check
|
directory: String path to the directory to check
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ You are a ReAct agent. You run in a loop and use ONE of the available functions
|
||||||
The result of that function call will be given to you in the next message.
|
The result of that function call will be given to you in the next message.
|
||||||
Call one function at a time. Function arguments can be complex objects, long strings, etc. if needed.
|
Call one function at a time. Function arguments can be complex objects, long strings, etc. if needed.
|
||||||
Each tool call you make shall be different from the previous.
|
Each tool call you make shall be different from the previous.
|
||||||
The user cannot see the results of function calls, so you have to explicitly use a tool like ask_human if you want them to see something.
|
The user cannot see the results of function calls, so you have to explicitly use a tool (function call) if you want them to see something. If you don't know what to do, just make a best guess on what function to call.
|
||||||
|
|
||||||
YOU MUST ALWAYS RESPOND WITH A SINGLE LINE OF PYTHON THAT CALLS ONE OF THE AVAILABLE TOOLS.
|
YOU MUST ALWAYS RESPOND WITH A SINGLE LINE OF PYTHON THAT CALLS ONE OF THE AVAILABLE TOOLS.
|
||||||
NEVER RETURN AN EMPTY MESSAGE.
|
NEVER RETURN AN EMPTY MESSAGE.
|
||||||
|
|
|
||||||
|
|
@ -185,9 +185,10 @@ def list_directory_tree(
|
||||||
exclude_patterns: List[str] = None,
|
exclude_patterns: List[str] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""List directory contents in a tree format with optional metadata.
|
"""List directory contents in a tree format with optional metadata.
|
||||||
|
If a file path is provided, returns information about just that file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
path: Directory path to list
|
path: Directory or file path to list
|
||||||
max_depth: Maximum depth to traverse (default: 1 for no recursion)
|
max_depth: Maximum depth to traverse (default: 1 for no recursion)
|
||||||
follow_links: Whether to follow symbolic links
|
follow_links: Whether to follow symbolic links
|
||||||
show_size: Show file sizes (default: False)
|
show_size: Show file sizes (default: False)
|
||||||
|
|
@ -200,24 +201,38 @@ def list_directory_tree(
|
||||||
root_path = Path(path).resolve()
|
root_path = Path(path).resolve()
|
||||||
if not root_path.exists():
|
if not root_path.exists():
|
||||||
raise ValueError(f"Path does not exist: {path}")
|
raise ValueError(f"Path does not exist: {path}")
|
||||||
if not root_path.is_dir():
|
|
||||||
raise ValueError(f"Path is not a directory: {path}")
|
# Load .gitignore patterns if present (only needed for directories)
|
||||||
|
spec = None
|
||||||
# Load .gitignore patterns if present
|
if root_path.is_dir():
|
||||||
spec = load_gitignore_patterns(root_path)
|
spec = load_gitignore_patterns(root_path)
|
||||||
|
# Create tree for directory
|
||||||
# Create tree
|
tree = Tree(f"📁 {root_path}/")
|
||||||
tree = Tree(f"📁 {root_path}/")
|
config = DirScanConfig(
|
||||||
config = DirScanConfig(
|
max_depth=max_depth,
|
||||||
max_depth=max_depth,
|
follow_links=follow_links,
|
||||||
follow_links=follow_links,
|
show_size=show_size,
|
||||||
show_size=show_size,
|
show_modified=show_modified,
|
||||||
show_modified=show_modified,
|
exclude_patterns=DEFAULT_EXCLUDE_PATTERNS + (exclude_patterns or []),
|
||||||
exclude_patterns=DEFAULT_EXCLUDE_PATTERNS + (exclude_patterns or []),
|
)
|
||||||
)
|
# Build tree
|
||||||
|
build_tree(root_path, tree, config, 0, spec)
|
||||||
# Build tree
|
else:
|
||||||
build_tree(root_path, tree, config, 0, spec)
|
# Create a simple tree for a single file
|
||||||
|
tree = Tree(f"🗋 {root_path.parent}/")
|
||||||
|
file_text = root_path.name
|
||||||
|
|
||||||
|
# Add size information if requested
|
||||||
|
if show_size:
|
||||||
|
size_str = format_size(root_path.stat().st_size)
|
||||||
|
file_text = f"{file_text} ({size_str})"
|
||||||
|
|
||||||
|
# Add modified time if requested
|
||||||
|
if show_modified:
|
||||||
|
mod_time = format_time(root_path.stat().st_mtime)
|
||||||
|
file_text = f"{file_text} [Modified: {mod_time}]"
|
||||||
|
|
||||||
|
tree.add(file_text)
|
||||||
|
|
||||||
# Capture tree output
|
# Capture tree output
|
||||||
with console.capture() as capture:
|
with console.capture() as capture:
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,5 @@ def test_invalid_path():
|
||||||
"""Test error handling for invalid paths"""
|
"""Test error handling for invalid paths"""
|
||||||
with pytest.raises(ValueError, match="Path does not exist"):
|
with pytest.raises(ValueError, match="Path does not exist"):
|
||||||
list_directory_tree.invoke({"path": "/nonexistent/path"})
|
list_directory_tree.invoke({"path": "/nonexistent/path"})
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="Path is not a directory"):
|
# We now allow files to be passed to list_directory_tree, so we don't test for this case anymore
|
||||||
list_directory_tree.invoke(
|
|
||||||
{"path": __file__}
|
|
||||||
) # Try to list the test file itself
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
"""Test for list_directory_tree with file path support."""
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ra_aid.tools import list_directory_tree
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_directory_tree_with_file():
|
||||||
|
"""Test that list_directory_tree works with a file path."""
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
|
||||||
|
tmp_file.write(b"Some test content")
|
||||||
|
tmp_file_path = tmp_file.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Test with file path
|
||||||
|
result = list_directory_tree.invoke({"path": tmp_file_path})
|
||||||
|
|
||||||
|
# Basic verification that the output contains the filename
|
||||||
|
filename = os.path.basename(tmp_file_path)
|
||||||
|
assert filename in result
|
||||||
|
|
||||||
|
# Test with size option
|
||||||
|
result_with_size = list_directory_tree.invoke({"path": tmp_file_path, "show_size": True})
|
||||||
|
assert "(" in result_with_size # Size information should be present
|
||||||
|
|
||||||
|
# Test with modified time option
|
||||||
|
result_with_time = list_directory_tree.invoke({"path": tmp_file_path, "show_modified": True})
|
||||||
|
assert "Modified:" in result_with_time
|
||||||
|
finally:
|
||||||
|
# Clean up the temporary file
|
||||||
|
if os.path.exists(tmp_file_path):
|
||||||
|
os.unlink(tmp_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_list_directory_tree_with_file()
|
||||||
|
print("All tests passed!")
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
"""Test file listing for non-git directories."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ra_aid.file_listing import get_file_listing
|
||||||
|
|
||||||
|
|
||||||
|
def test_non_git_file_listing():
|
||||||
|
"""Test that file listing works correctly for non-git directories."""
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
# Create a few test files
|
||||||
|
file1 = Path(temp_dir) / "file1.txt"
|
||||||
|
file2 = Path(temp_dir) / "file2.py"
|
||||||
|
file3 = Path(temp_dir) / ".hidden_file" # Hidden file
|
||||||
|
|
||||||
|
# Create a subdirectory with files
|
||||||
|
subdir = Path(temp_dir) / "subdir"
|
||||||
|
os.makedirs(subdir)
|
||||||
|
file4 = subdir / "file4.js"
|
||||||
|
|
||||||
|
# Create excluded directories
|
||||||
|
ra_aid_dir = Path(temp_dir) / ".ra-aid"
|
||||||
|
venv_dir = Path(temp_dir) / ".venv"
|
||||||
|
os.makedirs(ra_aid_dir)
|
||||||
|
os.makedirs(venv_dir)
|
||||||
|
|
||||||
|
# Create files in excluded directories
|
||||||
|
ra_aid_file = ra_aid_dir / "config.json"
|
||||||
|
venv_file = venv_dir / "activate"
|
||||||
|
|
||||||
|
# Write content to all files
|
||||||
|
for file_path in [file1, file2, file3, file4, ra_aid_file, venv_file]:
|
||||||
|
with open(file_path, "w") as f:
|
||||||
|
f.write("test content")
|
||||||
|
|
||||||
|
# Test regular file listing (should exclude hidden files and directories)
|
||||||
|
files, count = get_file_listing(temp_dir)
|
||||||
|
assert count == 3 # file1.txt, file2.py, subdir/file4.js
|
||||||
|
assert set(files) == {"file1.txt", "file2.py", os.path.join("subdir", "file4.js")}
|
||||||
|
|
||||||
|
# Test with include_hidden=True
|
||||||
|
files_with_hidden, count_with_hidden = get_file_listing(temp_dir, include_hidden=True)
|
||||||
|
assert count_with_hidden == 4 # Including .hidden_file
|
||||||
|
assert ".hidden_file" in files_with_hidden
|
||||||
|
|
||||||
|
# Test with limit
|
||||||
|
files_limited, count_limited = get_file_listing(temp_dir, limit=2)
|
||||||
|
assert len(files_limited) == 2
|
||||||
|
assert count_limited == 3 # Total count should still be 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_non_git_directory_with_excluded_dirs():
|
||||||
|
"""Test that excluded directories are properly handled in non-git directories."""
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
# Create regular files
|
||||||
|
file1 = Path(temp_dir) / "file1.txt"
|
||||||
|
with open(file1, "w") as f:
|
||||||
|
f.write("test content")
|
||||||
|
|
||||||
|
# Create excluded directories with files
|
||||||
|
excluded_dirs = [".ra-aid", ".venv", ".git", ".aider", "__pycache__"]
|
||||||
|
for excluded_dir in excluded_dirs:
|
||||||
|
dir_path = Path(temp_dir) / excluded_dir
|
||||||
|
os.makedirs(dir_path)
|
||||||
|
with open(dir_path / "test_file.txt", "w") as f:
|
||||||
|
f.write("test content")
|
||||||
|
|
||||||
|
# Get file listing
|
||||||
|
files, count = get_file_listing(temp_dir)
|
||||||
|
|
||||||
|
# Should only include the regular file
|
||||||
|
assert count == 1
|
||||||
|
assert files == ["file1.txt"]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pytest.main(["-xvs", __file__])
|
||||||
Loading…
Reference in New Issue