1- from typing import Optional , TypedDict
1+ from typing import Optional
22
33from langchain_core .callbacks import CallbackManagerForToolRun
44from langchain_core .tools import BaseTool
5- from pydantic import BaseModel , Field , PrivateAttr
5+ from pydantic import BaseModel , Field
66
77from blarify .repositories .graph_db_manager .db_manager import AbstractDbManager
88
99
10- class TreeNode (TypedDict ):
11- name : str
12- node_id : str | None
13- is_folder : bool
14- children : dict [str , "TreeNode" ]
15-
16-
17- class DirectoryChild (BaseModel ):
18- name : str
19- path : str
20- node_id : str
21- is_folder : bool
22-
23-
2410class Input (BaseModel ):
2511 path : str = Field (
2612 default = "/" ,
@@ -34,15 +20,12 @@ class GetDirectoryTree(BaseTool):
3420 "List the contents of a folder in the repository. "
3521 "Shows immediate children (files and subfolders) with their node IDs. "
3622 "Use the returned IDs with get_code_analysis to inspect specific files. "
37- "Call repeatedly with different paths to explore and expand the tree. "
38- "The tree accumulates across calls, showing all explored paths."
23+ "Call repeatedly with different paths to explore the codebase structure."
3924 )
4025 db_manager : AbstractDbManager = Field (description = "Database manager for queries" )
4126
4227 args_schema : type [BaseModel ] = Input # type: ignore[assignment]
4328
44- _explored_paths : dict [str , list [DirectoryChild ]] = PrivateAttr (default_factory = dict )
45-
4629 def _run (
4730 self ,
4831 path : str = "/" ,
@@ -51,20 +34,14 @@ def _run(
5134 """List contents of a folder in the repository."""
5235 normalized_path = self ._normalize_path (path )
5336
54- if normalized_path not in self ._explored_paths :
55- children = self ._query_folder_contents (normalized_path )
37+ children = self ._query_folder_contents (normalized_path )
5638
57- if not children :
58- if normalized_path == "/" and not self ._explored_paths :
59- return "No files found in the repository root."
60- if not self ._explored_paths :
61- return f"Folder '{ normalized_path } ' not found or is empty."
62- return self ._render_accumulated_tree ()
39+ if not children :
40+ if normalized_path == "/" :
41+ return "No files found in the repository root."
42+ return f"Folder '{ normalized_path } ' not found or is empty."
6343
64- parsed_children = self ._parse_children (children )
65- self ._explored_paths [normalized_path ] = parsed_children
66-
67- return self ._render_accumulated_tree ()
44+ return self ._format_tree_output (normalized_path , children )
6845
6946 def _normalize_path (self , path : str ) -> str :
7047 """Normalize the input path."""
@@ -96,115 +73,23 @@ def _query_folder_contents(self, path: str) -> list[dict[str, str | list[str]]]:
9673 results = self .db_manager .query (query , parameters = {"path" : path })
9774 return results
9875
99- def _parse_children (
100- self , children : list [dict [str , str | list [str ]]]
101- ) -> list [DirectoryChild ]:
102- """Parse query results into DirectoryChild objects."""
103- parsed : list [DirectoryChild ] = []
104- for child in children :
105- labels = child .get ("labels" , [])
106- is_folder = "FOLDER" in labels if isinstance (labels , list ) else False
107- name = child .get ("name" , "unknown" )
108- path = child .get ("path" , "" )
109- node_id = child .get ("id" , "unknown" )
76+ def _format_tree_output (
77+ self , path : str , children : list [dict [str , str | list [str ]]]
78+ ) -> str :
79+ """Format the results as an ASCII tree."""
80+ lines : list [str ] = [path ]
11081
111- parsed .append (
112- DirectoryChild (
113- name = str (name ),
114- path = str (path ),
115- node_id = str (node_id ),
116- is_folder = is_folder ,
117- )
118- )
119- return parsed
120-
121- def _render_accumulated_tree (self ) -> str :
122- """Render the full accumulated tree."""
123- if not self ._explored_paths :
124- return "No paths explored yet."
125-
126- tree = self ._build_tree_structure ()
127- return self ._render_tree_node (tree )
128-
129- def _build_tree_structure (self ) -> dict [str , TreeNode ]:
130- """Build a nested tree structure from explored paths."""
131- tree : dict [str , TreeNode ] = {
132- "/" : {"name" : "/" , "node_id" : None , "is_folder" : True , "children" : {}}
133- }
134-
135- for parent_path , children in sorted (self ._explored_paths .items ()):
136- parent_node = self ._get_or_create_node (tree , parent_path )
137- parent_children = parent_node ["children" ]
138-
139- for child in children :
140- parent_children [child .name ] = {
141- "name" : child .name ,
142- "node_id" : child .node_id ,
143- "is_folder" : child .is_folder ,
144- "children" : {},
145- }
146-
147- return tree
148-
149- def _get_or_create_node (
150- self , tree : dict [str , TreeNode ], path : str
151- ) -> TreeNode :
152- """Get or create a node at the given path."""
153- if path == "/" :
154- return tree ["/" ]
155-
156- parts = path .strip ("/" ).split ("/" )
157- current = tree ["/" ]
158-
159- for part in parts :
160- children = current ["children" ]
161-
162- if part not in children :
163- children [part ] = {
164- "name" : part ,
165- "node_id" : None ,
166- "is_folder" : True ,
167- "children" : {},
168- }
169- current = children [part ]
170-
171- return current
172-
173- def _render_tree_node (self , tree : dict [str , TreeNode ]) -> str :
174- """Recursively render a tree node."""
175- lines : list [str ] = []
176- root = tree .get ("/" )
177- if root :
178- lines .append ("/" )
179- lines .extend (self ._render_children (root ["children" ], "" ))
180- return "\n " .join (lines )
82+ for i , child in enumerate (children ):
83+ is_last = i == len (children ) - 1
84+ prefix = "└── " if is_last else "├── "
18185
182- def _render_children (
183- self , children : dict [str , TreeNode ], prefix : str
184- ) -> list [str ]:
185- """Render children nodes."""
186- lines : list [str ] = []
187- sorted_children = sorted (
188- children .items (),
189- key = lambda x : (0 if x [1 ]["is_folder" ] else 1 , x [0 ].lower ()),
190- )
191-
192- for i , (name , node ) in enumerate (sorted_children ):
193- is_last = i == len (sorted_children ) - 1
194- connector = "└── " if is_last else "├── "
195- child_prefix = prefix + (" " if is_last else "│ " )
196-
197- is_folder = node ["is_folder" ]
86+ labels = child .get ("labels" , [])
87+ is_folder = "FOLDER" in labels if isinstance (labels , list ) else False
19888 icon = "📁" if is_folder else "📄"
199- node_id = node ["node_id" ]
20089
201- if node_id :
202- lines .append (f"{ prefix } { connector } { icon } { name } [id: { node_id } ]" )
203- else :
204- lines .append (f"{ prefix } { connector } { icon } { name } " )
90+ name = child .get ("name" , "unknown" )
91+ node_id = child .get ("id" , "unknown" )
20592
206- node_children = node ["children" ]
207- if node_children :
208- lines .extend (self ._render_children (node_children , child_prefix ))
93+ lines .append (f"{ prefix } { icon } { name } [id: { node_id } ]" )
20994
210- return lines
95+ return " \n " . join ( lines )
0 commit comments