@@ -35,25 +35,38 @@ def apply(self) -> str:
3535
3636class GetSymbolsOverviewTool (Tool , ToolMarkerSymbolicRead ):
3737 """
38- Gets an overview of the top-level symbols defined in a given file.
38+ Gets an overview of the top-level symbols defined in a given file or directory .
3939 """
4040
4141 symbol_dict_grouper = LanguageServerSymbolDictGrouper (["kind" ], ["kind" ], collapse_singleton = True )
4242
43- def apply (self , relative_path : str , depth : int = 0 , max_answer_chars : int = - 1 ) -> str :
43+ def apply (self , relative_path : str , depth : int = 0 , max_answer_chars : int = - 1 , max_files : int = 20 ) -> str :
4444 """
45- Use this tool to get a high-level understanding of the code symbols in a file.
45+ Use this tool to get a high-level understanding of the code symbols in a file or directory .
4646 This should be the first tool to call when you want to understand a new file, unless you already know
4747 what you are looking for.
48+ When given a directory path, returns top-level symbols for every analyzable file in the directory.
4849
49- :param relative_path: the relative path to the file to get the overview of
50+ :param relative_path: the relative path to the file or directory to get the overview of
5051 :param depth: depth up to which descendants of top-level symbols shall be retrieved
5152 (e.g. 1 retrieves immediate children). Default 0.
5253 :param max_answer_chars: if the overview is longer than this number of characters,
5354 no content will be returned. -1 means the default value from the config will be used.
5455 Don't adjust unless there is really no other way to get the content required for the task.
56+ :param max_files: only used when relative_path is a directory. If the directory contains more
57+ analyzable files than this limit, the tool raises ValueError instead of returning a partial
58+ overview — narrow the path to a subdirectory, or learn the layout from memories first.
59+ Default 20. Don't increase unless you really need a broad sweep and accept the token cost.
5560 :return: a JSON object containing symbols grouped by kind in a compact format.
61+ For directories, returns a mapping of file paths to their grouped symbols.
5662 """
63+ file_path = os .path .join (self .project .project_root , relative_path )
64+ if not os .path .exists (file_path ):
65+ raise FileNotFoundError (f"File or directory { relative_path } does not exist in the project." )
66+
67+ if os .path .isdir (file_path ):
68+ return self ._apply_directory (relative_path , depth = depth , max_answer_chars = max_answer_chars , max_files = max_files )
69+
5770 result = self .get_symbol_overview (relative_path , depth = depth )
5871
5972 # capture kind names and depth-0 snapshots before grouping, which mutates the dicts
@@ -82,6 +95,49 @@ def make_depth_0_result() -> str:
8295
8396 return self ._limit_length (result_json_str , max_answer_chars , shortened_result_factories = shortened_results )
8497
98+ def _apply_directory (self , relative_path : str , depth : int = 0 , max_answer_chars : int = - 1 , max_files : int = 20 ) -> str :
99+ symbol_retriever = self .create_language_server_symbol_retriever ()
100+ path_to_symbols = symbol_retriever .get_symbol_overview (relative_path )
101+
102+ total_files = len (path_to_symbols )
103+ if total_files > max_files :
104+ sample = list (path_to_symbols .keys ())[:5 ]
105+ raise ValueError (
106+ f"Directory { relative_path } contains { total_files } analyzable files, which exceeds "
107+ f"max_files={ max_files } . Narrow the path to a more specific subdirectory, or learn the "
108+ f"repository layout from memories before asking for a broad overview. "
109+ f"Sample files found: { sample } "
110+ )
111+
112+ def child_inclusion_predicate (s : LanguageServerSymbol ) -> bool :
113+ return not s .is_low_level ()
114+
115+ per_file_result = {}
116+ file_count = 0
117+ for file_rel_path , symbols in path_to_symbols .items ():
118+ symbol_dicts = []
119+ for symbol in symbols :
120+ symbol_dicts .append (
121+ symbol .to_dict (
122+ name_path = False ,
123+ name = True ,
124+ depth = depth ,
125+ kind = True ,
126+ relative_path = False ,
127+ location = False ,
128+ child_inclusion_predicate = child_inclusion_predicate ,
129+ )
130+ )
131+ per_file_result [file_rel_path ] = self .symbol_dict_grouper .group (symbol_dicts )
132+ file_count += 1
133+
134+ result_json_str = self ._to_json (per_file_result )
135+
136+ def make_file_counts () -> str :
137+ return f"Analyzed { file_count } files in directory { relative_path } "
138+
139+ return self ._limit_length (result_json_str , max_answer_chars , shortened_result_factories = [make_file_counts ])
140+
85141 def get_symbol_overview (self , relative_path : str , depth : int = 0 ) -> list [LanguageServerSymbol .OutputDict ]:
86142 """
87143 :param relative_path: relative path to a source file
@@ -90,8 +146,6 @@ def get_symbol_overview(self, relative_path: str, depth: int = 0) -> list[Langua
90146 """
91147 symbol_retriever = self .create_language_server_symbol_retriever ()
92148
93- # The symbol overview is capable of working with both files and directories,
94- # but we want to ensure that the user provides a file path.
95149 file_path = os .path .join (self .project .project_root , relative_path )
96150 if not os .path .exists (file_path ):
97151 raise FileNotFoundError (f"File or directory { relative_path } does not exist in the project." )
0 commit comments