Skip to content

Commit b1c42a3

Browse files
committed
respect .gitignore when returning directory_tree to prevent infinitely long node_models
1 parent fb746d6 commit b1c42a3

1 file changed

Lines changed: 37 additions & 6 deletions

File tree

llms/extensions/computer/filesystem.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ def directory_tree(
406406
) -> str:
407407
"""
408408
Get a recursive tree view of files and directories as a JSON structure. Each entry includes 'name', 'type' (file/directory), and 'children' for directories.
409-
Files have no children array, while directories always have a children array (which may be empty).
409+
Files have no children array, while directories always have a children array (which may be empty). Respects any .gitignore rules from the root directory together with any exclude_patterns.
410410
The output is formatted with 2-space indentation for readability. Only works within allowed directories.
411411
"""
412412
import json
@@ -416,21 +416,52 @@ def directory_tree(
416416
if exclude_patterns is None:
417417
exclude_patterns = []
418418

419+
def _parse_gitignore(directory: str) -> List[str]:
420+
gitignore_path = os.path.join(directory, ".gitignore")
421+
patterns = []
422+
if os.path.exists(gitignore_path) and os.path.isfile(gitignore_path):
423+
try:
424+
with open(gitignore_path, encoding="utf-8") as f:
425+
for line in f:
426+
line = line.strip()
427+
if line and not line.startswith("#"):
428+
patterns.append(line)
429+
except Exception as e:
430+
logger.warning(f"Error reading .gitignore in {directory}: {e}")
431+
return patterns
432+
433+
# Parse .gitignore only in the root directory (Simple Global Mappings)
434+
gitignore_patterns = _parse_gitignore(valid_path)
435+
all_exclude_patterns = exclude_patterns + gitignore_patterns
436+
419437
def _build_tree(current_path: str) -> List[Dict[str, Any]]:
420438
entries = []
421439
try:
422440
with os.scandir(current_path) as it:
423441
items = sorted(it, key=lambda x: x.name)
424442

425443
for entry in items:
426-
# Check exclusion
427-
rel_path = entry.path[root_path_len:]
428-
# Check against patterns
444+
# 1. Check exclusion patterns
445+
rel_path_from_root = entry.path[root_path_len:]
446+
429447
should_exclude = False
430-
for pattern in exclude_patterns:
431-
if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch(entry.name, pattern):
448+
for pattern in all_exclude_patterns:
449+
# Match against relative path or name
450+
# Support ending with / for directory matching
451+
is_dir_pattern = pattern.endswith("/")
452+
norm_pattern = pattern.rstrip("/")
453+
454+
if fnmatch.fnmatch(rel_path_from_root, pattern) or fnmatch.fnmatch(entry.name, pattern):
432455
should_exclude = True
433456
break
457+
458+
# Handle patterns like "node_modules/" matching "node_modules" directory
459+
if fnmatch.fnmatch(entry.name, norm_pattern):
460+
if is_dir_pattern and not entry.is_dir():
461+
continue
462+
should_exclude = True
463+
break
464+
434465
if should_exclude:
435466
continue
436467

0 commit comments

Comments
 (0)