Skip to content

Conversation

@ammar-agent
Copy link
Collaborator

Generated with cmux

Summary

Adds file_list tool for directory exploration without spawning bash processes. Returns recursive JSON tree structure that models can easily interpret.

Key Features

Recursive structure: Results nest via children arrays - models see hierarchy directly without parsing paths.

Gitignore by default: Respects .gitignore patterns automatically (.git always hidden). Set gitignore: false to see all files.

Pattern filtering: Glob support (*.ts, **/*.test.ts). Prunes empty directories when patterns are used.

Limit enforcement: 128 entry hard cap. Fails with actionable error message instead of truncating (matches bash tool philosophy).

UI: Tree visualization with file sizes and proper indentation.

Implementation

  • Core: src/services/tools/file_list.ts (273 lines)
  • Tests: 18 test cases covering recursion, filtering, limits, gitignore, errors
  • Dependencies: Added minimatch (glob), ignore (.gitignore parsing)
  • UI: FileListToolCall component with CSS modules

Example Usage

// Quick scan (depth 1, default)
file_list({ path: "src/components" })

// Find TypeScript files
file_list({ 
  path: "src", 
  pattern: "*.ts",
  max_depth: 3,
  max_entries: 50 
})

// Show all files (ignore .gitignore)
file_list({ 
  path: ".", 
  gitignore: false 
})

Testing

bun test src/services/tools/file_list.test.ts  # 18/18 pass
make typecheck  #
make fmt-check  #

@ammar-agent ammar-agent force-pushed the add-file-list-tool branch 3 times, most recently from 3d6c7ef to 5bc3ea3 Compare October 17, 2025 15:03
- Implements recursive directory listing with depth control
- Returns nested JSON structure for easy model interpretation
- Respects .gitignore patterns by default (configurable)
- Supports glob pattern filtering (*.ts, **/*.test.ts)
- Enforces hard limit of 128 entries (fails fast vs truncate)
- Always hides .git directory
- Includes comprehensive test suite (18 tests)
- UI component with tree visualization
- Replace CSS modules with Emotion styled components (matches project pattern)
- Use shared ToolPrimitives and toolUtils for consistent styling
- Reduce DEFAULT_MAX_ENTRIES from 100 to 64
- Update tests to reflect new limit
The ig (ignore instance) is loaded once from .gitignore and passed
through recursion for efficiency. This avoids re-parsing .gitignore
at every directory level.
- Remove ig and rootPath from TraversalOptions interface
- Convert buildFileTree to inner function that captures these via closure
- Cleaner interface - only exposes actual options, not implementation details
- Same efficiency - still loads .gitignore once
- Net: -5 lines
- Remove unused FileListToolArgs import
- Remove unused 'err' variables in catch blocks
- Replace {} as any with proper AbortController signal in tests
- Return formatted tree string in 'output' field instead of recursive JSON 'entries'
- Saves ~50% tokens for typical listings
- Format function handles tree characters (├─, └─, │) and file sizes
- Updated UI to display pre-formatted output string
- Updated all tests to validate string output instead of structured data
The file_list tool is already handled by GenericToolCall at the end of
ToolMessage.tsx, so the custom FileListToolCall component and routing
were never actually used. Removed to fix lint errors.
- Add import for FileListToolCall component
- Add isFileListTool type guard using Zod schema
- Add routing case for file_list tool
- Fix status type: use ToolStatus instead of custom status strings
- Update status checks: 'completed'/'failed'/'executing' instead of 'complete'/'error'/'streaming'

Fixes component routing so file_list tool calls render correctly in UI.
…tern

- Add ToolIcon component in shared/ToolIcon.tsx
- Update all tool components to use ToolIcon (except GenericToolCall)
  - BashToolCall: 🔧 bash
  - FileReadToolCall: 📖 file_read
  - FileListToolCall: 📖 file_list (changed from 📋)
  - FileEditToolCall: ✏️ file_edit_*
  - TodoToolCall: 📋 todo_write
  - ProposePlanToolCall: 📋 propose_plan (added tooltip)
- GenericToolCall remains unchanged (text-only fallback)
- Consistent tooltip pattern: hover over emoji shows full tool name

This abstracts the emoji+tooltip pattern into a single reusable component,
ensures consistency across all specialized tool displays, and unifies the
file_read and file_list emojis to 📖.
Add trailing slash to displayed path in FileListToolCall to clearly
indicate it's a directory being listed. Only affects display - the
actual tool arg remains unchanged.
Change formatSize() to use Math.round() instead of toFixed(1):
- Before: 1.5KB, 2.3MB
- After: 2KB, 2MB

No decimals in file sizes preserves tokens in LLM output without
sacrificing meaningful precision for directory listings. Tests still
pass as they use regex patterns matching both formats.
Replace fs.readdir() with fs.opendir() async iterator:
- More memory efficient: doesn't allocate full array for large directories
- Early termination: stops reading if we collect 2x the limit (accounts for filtering)
- Proper cleanup: uses finally block to ensure dirHandle.close()

For huge directories (1000+ files), this prevents unnecessary memory allocation
when we only need the first 64 entries. The 2x multiplier ensures we read enough
entries to account for gitignore filtering and pattern matching.
Replace try/finally with 'using' declaration for automatic resource cleanup.
This prevents ERR_DIR_CLOSED errors when the async iterator auto-closes
the directory handle and then finally block tries to close it again.

Key changes:
- Wrap Dir in AsyncDisposable object with Symbol.asyncDispose
- Use Promise.resolve() to handle Bun's synchronous close() behavior
- Properly catch errors if handle is already closed
- Tests verify correct cleanup in all scenarios (early break, full iteration)

The 'using' pattern ensures cleanup happens at scope exit, even with
early returns or exceptions, while gracefully handling double-close.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant