Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@
dist/

# Agent outputs (generated at runtime)
outputs/logs/*.log
outputs/logs/*.md
outputs/logs/*.jsonl
outputs/reports/*.md
outputs/reports/*.jsonl
outputs/

# Environment
.env
Expand Down
9 changes: 7 additions & 2 deletions internal/tools/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ if err != nil {
return nil
}
name := d.Name()
if name == "node_modules" || name == ".git" || strings.HasPrefix(name, ".") {
// Skip hidden files/directories and special directories, but not the root directory
if path != dir && (name == "node_modules" || name == ".git" || strings.HasPrefix(name, ".")) {
if d.IsDir() {
return filepath.SkipDir
}
Expand All @@ -210,7 +211,11 @@ return fmt.Errorf("limit reached")
if ext != "" && filepath.Ext(name) != ext {
return nil
}
rel, _ := filepath.Rel(".", path)
rel, _ := filepath.Rel(dir, path)
// Skip the root directory itself (it will be ".")
if rel == "." {
return nil
}
if d.IsDir() {
files = append(files, rel+"/")
} else {
Expand Down
169 changes: 169 additions & 0 deletions internal/tools/tools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,3 +473,172 @@ func TestExecuteDirect_Grep(t *testing.T) {
t.Fatalf("expected match, got: %s", r.Output)
}
}

// ── list_files tests ──

func TestListFiles_Basic(t *testing.T) {
dir := t.TempDir()
os.WriteFile(filepath.Join(dir, "a.txt"), []byte(""), 0o644)
os.WriteFile(filepath.Join(dir, "b.go"), []byte(""), 0o644)
sub := filepath.Join(dir, "sub")
os.MkdirAll(sub, 0o755)
os.WriteFile(filepath.Join(sub, "c.md"), []byte(""), 0o644)

// Save original working directory
originalWd, _ := os.Getwd()
defer os.Chdir(originalWd)

// Change to a different directory to test relative paths
os.Chdir("/tmp")
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test changes the working directory to a hard-coded /tmp, which can make the test non-hermetic and fail in environments without that path (or with restricted permissions). Prefer using os.TempDir() or another t.TempDir()-derived directory as the “different cwd” instead of assuming /tmp exists.

Suggested change
os.Chdir("/tmp")
otherWd := t.TempDir()
os.Chdir(otherWd)

Copilot uses AI. Check for mistakes.

r := listFiles(map[string]string{
"directory": dir,
}, 0)

if !r.Success {
t.Fatalf("expected success, got error: %s", r.Error)
}

// Check that paths are relative to 'dir', not to cwd
lines := strings.Split(strings.TrimSpace(r.Output), "\n")
expected := []string{"a.txt", "b.go", "sub/", "sub/c.md"}
if len(lines) != len(expected) {
t.Fatalf("expected %d lines, got %d: %s", len(expected), len(lines), r.Output)
}

for i, line := range lines {
if line != expected[i] {
t.Errorf("line %d: got %q, want %q", i, line, expected[i])
}
}
}

func TestListFiles_FromWithinDirectory(t *testing.T) {
dir := t.TempDir()
os.WriteFile(filepath.Join(dir, "test.txt"), []byte(""), 0o644)

// Save original working directory
originalWd, _ := os.Getwd()
defer os.Chdir(originalWd)

// Change to the test directory
os.Chdir(dir)

r := listFiles(map[string]string{
"directory": ".",
}, 0)

if !r.Success {
t.Fatalf("expected success, got error: %s", r.Error)
}

// Should list files relative to current directory (.)
lines := strings.Split(strings.TrimSpace(r.Output), "\n")
if len(lines) != 1 || lines[0] != "test.txt" {
t.Fatalf("expected ['test.txt'], got %v", lines)
}
}

func TestListFiles_ExtensionFilter(t *testing.T) {
dir := t.TempDir()
os.WriteFile(filepath.Join(dir, "a.txt"), []byte(""), 0o644)
os.WriteFile(filepath.Join(dir, "b.go"), []byte(""), 0o644)
os.WriteFile(filepath.Join(dir, "c.go"), []byte(""), 0o644)

r := listFiles(map[string]string{
"directory": dir,
"extension": ".go",
}, 0)

if !r.Success {
t.Fatalf("expected success, got error: %s", r.Error)
}

lines := strings.Split(strings.TrimSpace(r.Output), "\n")
if len(lines) != 2 {
t.Fatalf("expected 2 .go files, got %d: %s", len(lines), r.Output)
}
for _, line := range lines {
if !strings.HasSuffix(line, ".go") {
t.Errorf("expected .go file, got %q", line)
}
}
}

func TestListFiles_EmptyDirectory(t *testing.T) {
dir := t.TempDir()

r := listFiles(map[string]string{
"directory": dir,
}, 0)

if !r.Success {
t.Fatalf("expected success, got error: %s", r.Error)
}
if r.Output != "(empty directory)" {
t.Fatalf("expected '(empty directory)', got %q", r.Output)
}
}

func TestListFiles_SkipsHiddenAndSpecialDirs(t *testing.T) {
dir := t.TempDir()
os.WriteFile(filepath.Join(dir, "normal.txt"), []byte(""), 0o644)
os.WriteFile(filepath.Join(dir, ".hidden"), []byte(""), 0o644)
gitDir := filepath.Join(dir, ".git")
os.MkdirAll(gitDir, 0o755)
os.WriteFile(filepath.Join(gitDir, "config"), []byte(""), 0o644)
nodeModules := filepath.Join(dir, "node_modules")
os.MkdirAll(nodeModules, 0o755)
os.WriteFile(filepath.Join(nodeModules, "package.json"), []byte(""), 0o644)

r := listFiles(map[string]string{
"directory": dir,
}, 0)

if !r.Success {
t.Fatalf("expected success, got error: %s", r.Error)
}

// Should only show normal.txt
lines := strings.Split(strings.TrimSpace(r.Output), "\n")
if len(lines) != 1 || lines[0] != "normal.txt" {
t.Fatalf("expected only 'normal.txt', got %v", lines)
}
}

func TestListFiles_SubdirectoryListing(t *testing.T) {
dir := t.TempDir()
sub := filepath.Join(dir, "subdir")
os.MkdirAll(sub, 0o755)
os.WriteFile(filepath.Join(sub, "file.txt"), []byte(""), 0o644)
os.WriteFile(filepath.Join(sub, "another.md"), []byte(""), 0o644)

r := listFiles(map[string]string{
"directory": sub,
}, 0)

if !r.Success {
t.Fatalf("expected success, got error: %s", r.Error)
}

// Should list files relative to subdir
lines := strings.Split(strings.TrimSpace(r.Output), "\n")
expected := []string{"file.txt", "another.md"}
if len(lines) != len(expected) {
t.Fatalf("expected %d lines, got %d: %s", len(expected), len(lines), r.Output)
}
// Check that we have both files (order may vary)
hasFileTxt := false
hasAnotherMd := false
for _, line := range lines {
if line == "file.txt" {
hasFileTxt = true
}
if line == "another.md" {
hasAnotherMd = true
}
}
if !hasFileTxt || !hasAnotherMd {
t.Errorf("missing expected files: got %v", lines)
}
}
1 change: 0 additions & 1 deletion outputs/logs/.gitkeep

This file was deleted.

1 change: 0 additions & 1 deletion outputs/reports/.gitkeep

This file was deleted.

Loading