ciayn fixes

This commit is contained in:
AI Christianson 2025-03-03 23:50:55 -05:00
parent e45cd78c7f
commit bd02bffc55
7 changed files with 228 additions and 71 deletions

View File

@ -74,14 +74,15 @@ def get_file_listing(
directory: str, limit: Optional[int] = None, include_hidden: bool = False
) -> 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)
and the total count of files.
Args:
directory: Path to the git repository
directory: Path to the directory
limit: Optional maximum number of files to return
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}")
# Check if it's a git repository
if not is_git_repo(directory):
return [], 0
is_git = is_git_repo(directory)
# 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 = []
for file in (
tracked_files_process.stdout.splitlines()
+ untracked_files_process.stdout.splitlines()
):
file = file.strip()
if not file:
continue
# Skip hidden files unless explicitly included
if not include_hidden and (
file.startswith(".")
or any(part.startswith(".") for part in file.split("/"))
if is_git:
# 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
for file in (
tracked_files_process.stdout.splitlines()
+ untracked_files_process.stdout.splitlines()
):
continue
# Skip .aider files
if ".aider" in file:
continue
all_files.append(file)
file = file.strip()
if not file:
continue
# Skip hidden files unless explicitly included
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
all_files = sorted(set(all_files))

View File

@ -28,7 +28,7 @@ def is_new_project(directory: str) -> bool:
A project is considered new if it either:
- 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:
directory: String path to the directory to check

View File

@ -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.
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.
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.
NEVER RETURN AN EMPTY MESSAGE.

View File

@ -185,9 +185,10 @@ def list_directory_tree(
exclude_patterns: List[str] = None,
) -> str:
"""List directory contents in a tree format with optional metadata.
If a file path is provided, returns information about just that file.
Args:
path: Directory path to list
path: Directory or file path to list
max_depth: Maximum depth to traverse (default: 1 for no recursion)
follow_links: Whether to follow symbolic links
show_size: Show file sizes (default: False)
@ -200,24 +201,38 @@ def list_directory_tree(
root_path = Path(path).resolve()
if not root_path.exists():
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
spec = load_gitignore_patterns(root_path)
# Load .gitignore patterns if present (only needed for directories)
spec = None
if root_path.is_dir():
spec = load_gitignore_patterns(root_path)
# Create tree for directory
tree = Tree(f"📁 {root_path}/")
config = DirScanConfig(
max_depth=max_depth,
follow_links=follow_links,
show_size=show_size,
show_modified=show_modified,
exclude_patterns=DEFAULT_EXCLUDE_PATTERNS + (exclude_patterns or []),
)
# Build tree
build_tree(root_path, tree, config, 0, spec)
else:
# Create a simple tree for a single file
tree = Tree(f"🗋 {root_path.parent}/")
file_text = root_path.name
# Create tree
tree = Tree(f"📁 {root_path}/")
config = DirScanConfig(
max_depth=max_depth,
follow_links=follow_links,
show_size=show_size,
show_modified=show_modified,
exclude_patterns=DEFAULT_EXCLUDE_PATTERNS + (exclude_patterns or []),
)
# Add size information if requested
if show_size:
size_str = format_size(root_path.stat().st_size)
file_text = f"{file_text} ({size_str})"
# Build tree
build_tree(root_path, tree, config, 0, spec)
# 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
with console.capture() as capture:

View File

@ -131,7 +131,4 @@ def test_invalid_path():
with pytest.raises(ValueError, match="Path does not exist"):
list_directory_tree.invoke({"path": "/nonexistent/path"})
with pytest.raises(ValueError, match="Path is not a directory"):
list_directory_tree.invoke(
{"path": __file__}
) # Try to list the test file itself
# We now allow files to be passed to list_directory_tree, so we don't test for this case anymore

39
tests/test_list_file.py Normal file
View File

@ -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!")

View File

@ -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__])