@@ -75,6 +75,107 @@ def _get_repo_context_process_cache_key(git_provider, context_files: list, max_l
7575 return type (git_provider ).__name__ , pr_url , _get_repo_context_cache_key (context_files , max_lines )
7676
7777
78+ def _get_repo_context_config () -> tuple [list , int ] | None :
79+ context_files = get_settings ().config .get ("repo_context_files" , [])
80+ if not context_files :
81+ return None
82+
83+ if isinstance (context_files , str ):
84+ get_logger ().warning (
85+ "repo_context_files should be a list of file paths; treating string value as one file path" ,
86+ artifact = {"repo_context_files" : context_files },
87+ )
88+ context_files = [context_files ]
89+ elif not isinstance (context_files , list ):
90+ get_logger ().warning (
91+ "repo_context_files should be a list of file paths; skipping repo context" ,
92+ artifact = {"repo_context_files" : context_files },
93+ )
94+ return None
95+
96+ max_lines = get_settings ().config .get ("repo_context_max_lines" , 500 )
97+ try :
98+ max_lines = max (0 , int (max_lines ))
99+ except (TypeError , ValueError ):
100+ max_lines = 500
101+
102+ return context_files , max_lines
103+
104+
105+ def _provider_supports_repo_context (git_provider ) -> bool :
106+ provider_class = type (git_provider )
107+ if provider_class .get_repo_file_content is not GitProvider .get_repo_file_content :
108+ return True
109+
110+ if provider_class not in _unsupported_repo_context_provider_classes :
111+ _unsupported_repo_context_provider_classes .add (provider_class )
112+ get_logger ().warning (
113+ f"repo_context_files is configured, but { provider_class .__name__ } does not support repository "
114+ "file fetching; skipping repo context"
115+ )
116+ return False
117+
118+
119+ def _get_provider_repo_context_cache (git_provider ) -> _RepoContextCache :
120+ repo_context_cache = getattr (git_provider , REPO_CONTEXT_CACHE_ATTRIBUTE , None )
121+ if repo_context_cache is None or not isinstance (repo_context_cache , _RepoContextCache ):
122+ repo_context_cache = _RepoContextCache ()
123+ setattr (git_provider , REPO_CONTEXT_CACHE_ATTRIBUTE , repo_context_cache )
124+ return repo_context_cache
125+
126+
127+ def _get_cached_repo_context (git_provider , context_files : list , max_lines : int ):
128+ process_cache_key = _get_repo_context_process_cache_key (git_provider , context_files , max_lines )
129+ if process_cache_key is not None :
130+ cached_repo_context = _repo_context_process_cache .get (process_cache_key , _REPO_CONTEXT_CACHE_MISS )
131+ if cached_repo_context is not _REPO_CONTEXT_CACHE_MISS :
132+ return cached_repo_context
133+
134+ cache_key = _get_repo_context_cache_key (context_files , max_lines )
135+ cached_repo_context = _get_provider_repo_context_cache (git_provider ).get (cache_key , _REPO_CONTEXT_CACHE_MISS )
136+ if cached_repo_context is not _REPO_CONTEXT_CACHE_MISS :
137+ return cached_repo_context
138+
139+ return _REPO_CONTEXT_CACHE_MISS
140+
141+
142+ def _store_repo_context (git_provider , context_files : list , max_lines : int , repo_context : str ) -> None :
143+ cache_key = _get_repo_context_cache_key (context_files , max_lines )
144+ _get_provider_repo_context_cache (git_provider )[cache_key ] = repo_context
145+
146+ process_cache_key = _get_repo_context_process_cache_key (git_provider , context_files , max_lines )
147+ if process_cache_key :
148+ _repo_context_process_cache [process_cache_key ] = repo_context
149+
150+
151+ def _load_repo_context_files (git_provider , context_files : list ) -> tuple [dict [str , str ], bool ]:
152+ files = {}
153+ had_fetch_error = False
154+ for file_path in context_files :
155+ if not isinstance (file_path , str ) or not file_path .strip ():
156+ get_logger ().warning ("Skipping invalid repo context file path" , artifact = {"file_path" : file_path })
157+ continue
158+
159+ file_path = file_path .strip ()
160+ try :
161+ content = git_provider .get_repo_file_content (file_path )
162+ except Exception as e :
163+ had_fetch_error = True
164+ get_logger ().warning (f"Failed to load repo context file: { file_path } " , artifact = {"error" : str (e )})
165+ continue
166+
167+ if not content :
168+ get_logger ().debug (f"Repo context file is empty or missing: { file_path } " )
169+ continue
170+
171+ if isinstance (content , bytes ):
172+ content = content .decode ("utf-8" , errors = "replace" )
173+
174+ files [file_path ] = str (content ).rstrip ()
175+
176+ return files , had_fetch_error
177+
178+
78179def render_instruction_files (files : dict [str , str ]) -> str :
79180 parts = [
80181 INSTRUCTION_FILES_INTRO ,
@@ -139,88 +240,26 @@ def render_instruction_files_with_line_budget(files: dict[str, str], max_lines:
139240
140241
141242def build_repo_context (git_provider ) -> str :
142- context_files = get_settings ().config .get ("repo_context_files" , [])
143- if not context_files :
144- return ""
145- if isinstance (context_files , str ):
146- get_logger ().warning (
147- "repo_context_files should be a list of file paths; treating string value as one file path" ,
148- artifact = {"repo_context_files" : context_files },
149- )
150- context_files = [context_files ]
151- elif not isinstance (context_files , list ):
152- get_logger ().warning (
153- "repo_context_files should be a list of file paths; skipping repo context" ,
154- artifact = {"repo_context_files" : context_files },
155- )
243+ repo_context_config = _get_repo_context_config ()
244+ if repo_context_config is None :
156245 return ""
157246
158- provider_class = type (git_provider )
159- if provider_class .get_repo_file_content is GitProvider .get_repo_file_content :
160- if provider_class not in _unsupported_repo_context_provider_classes :
161- _unsupported_repo_context_provider_classes .add (provider_class )
162- get_logger ().warning (
163- f"repo_context_files is configured, but { provider_class .__name__ } does not support repository "
164- "file fetching; skipping repo context"
165- )
247+ context_files , max_lines = repo_context_config
248+ if not _provider_supports_repo_context (git_provider ):
166249 return ""
167250
168- max_lines = get_settings ().config .get ("repo_context_max_lines" , 500 )
169- try :
170- max_lines = max (0 , int (max_lines ))
171- except (TypeError , ValueError ):
172- max_lines = 500
173-
174- cache_key = _get_repo_context_cache_key (context_files , max_lines )
175- process_cache_key = _get_repo_context_process_cache_key (git_provider , context_files , max_lines )
176- if process_cache_key is not None :
177- cached_repo_context = _repo_context_process_cache .get (process_cache_key , _REPO_CONTEXT_CACHE_MISS )
178- if cached_repo_context is not _REPO_CONTEXT_CACHE_MISS :
179- return cached_repo_context
180-
181- repo_context_cache = getattr (git_provider , REPO_CONTEXT_CACHE_ATTRIBUTE , None )
182- if repo_context_cache is None or not isinstance (repo_context_cache , _RepoContextCache ):
183- repo_context_cache = _RepoContextCache ()
184- setattr (git_provider , REPO_CONTEXT_CACHE_ATTRIBUTE , repo_context_cache )
185- cached_repo_context = repo_context_cache .get (cache_key , _REPO_CONTEXT_CACHE_MISS )
251+ cached_repo_context = _get_cached_repo_context (git_provider , context_files , max_lines )
186252 if cached_repo_context is not _REPO_CONTEXT_CACHE_MISS :
187253 return cached_repo_context
188254
189- files = {}
190- had_fetch_error = False
191- for file_path in context_files :
192- if not isinstance (file_path , str ) or not file_path .strip ():
193- get_logger ().warning ("Skipping invalid repo context file path" , artifact = {"file_path" : file_path })
194- continue
195-
196- file_path = file_path .strip ()
197- try :
198- content = git_provider .get_repo_file_content (file_path )
199- except Exception as e :
200- had_fetch_error = True
201- get_logger ().warning (f"Failed to load repo context file: { file_path } " , artifact = {"error" : str (e )})
202- continue
203-
204- if not content :
205- get_logger ().debug (f"Repo context file is empty or missing: { file_path } " )
206- continue
207-
208- if isinstance (content , bytes ):
209- content = content .decode ("utf-8" , errors = "replace" )
210-
211- files [file_path ] = str (content ).rstrip ()
212-
255+ files , had_fetch_error = _load_repo_context_files (git_provider , context_files )
213256 if not files and had_fetch_error :
214257 return ""
215258
216259 if not files :
217- repo_context_cache [cache_key ] = ""
218- if process_cache_key :
219- _repo_context_process_cache [process_cache_key ] = ""
260+ _store_repo_context (git_provider , context_files , max_lines , "" )
220261 return ""
221262
222263 repo_context = render_instruction_files_with_line_budget (files , max_lines )
223- repo_context_cache [cache_key ] = repo_context
224- if process_cache_key :
225- _repo_context_process_cache [process_cache_key ] = repo_context
264+ _store_repo_context (git_provider , context_files , max_lines , repo_context )
226265 return repo_context
0 commit comments