@@ -221,6 +221,131 @@ def assess(self, repository: Repository) -> Finding:
221221 return Finding .error (self .attribute , reason = "Could not read .gitignore" )
222222
223223
224+ class FileSizeLimitsAssessor (BaseAssessor ):
225+ """Tier 2 - File size limits for context window optimization."""
226+
227+ @property
228+ def attribute_id (self ) -> str :
229+ return "file_size_limits"
230+
231+ @property
232+ def tier (self ) -> int :
233+ return 2
234+
235+ @property
236+ def attribute (self ) -> Attribute :
237+ return Attribute (
238+ id = self .attribute_id ,
239+ name = "File Size Limits" ,
240+ category = "Context Window Optimization" ,
241+ tier = self .tier ,
242+ description = "Files are reasonably sized for AI context windows" ,
243+ criteria = "<5% of files >500 lines, no files >1000 lines" ,
244+ default_weight = 0.03 ,
245+ )
246+
247+ def assess (self , repository : Repository ) -> Finding :
248+ """Check for excessively large files that strain context windows.
249+
250+ Scoring:
251+ - 100: All files <500 lines
252+ - 75-99: Some files 500-1000 lines
253+ - 0-74: Files >1000 lines exist
254+ """
255+ # Count files by size
256+ large_files = [] # 500-1000 lines
257+ huge_files = [] # >1000 lines
258+ total_files = 0
259+
260+ # Check common source file extensions
261+ extensions = {".py" , ".js" , ".ts" , ".jsx" , ".tsx" , ".go" , ".java" , ".rb" , ".rs" , ".cpp" , ".c" , ".h" }
262+
263+ for ext in extensions :
264+ pattern = f"**/*{ ext } "
265+ try :
266+ from pathlib import Path
267+ for file_path in repository .path .glob (pattern ):
268+ if file_path .is_file ():
269+ try :
270+ with open (file_path , "r" , encoding = "utf-8" ) as f :
271+ lines = len (f .readlines ())
272+ total_files += 1
273+
274+ if lines > 1000 :
275+ huge_files .append ((file_path .relative_to (repository .path ), lines ))
276+ elif lines > 500 :
277+ large_files .append ((file_path .relative_to (repository .path ), lines ))
278+ except (OSError , UnicodeDecodeError ):
279+ # Skip files we can't read
280+ pass
281+ except Exception :
282+ pass
283+
284+ if total_files == 0 :
285+ return Finding .not_applicable (
286+ self .attribute ,
287+ reason = "No source files found to assess" ,
288+ )
289+
290+ # Calculate score
291+ if huge_files :
292+ # Penalty for files >1000 lines
293+ percentage_huge = (len (huge_files ) / total_files ) * 100
294+ score = max (0 , 70 - (percentage_huge * 10 ))
295+ status = "fail"
296+ evidence = [
297+ f"Found { len (huge_files )} files >1000 lines ({ percentage_huge :.1f} % of { total_files } files)" ,
298+ f"Largest: { huge_files [0 ][0 ]} ({ huge_files [0 ][1 ]} lines)" ,
299+ ]
300+ elif large_files :
301+ # Partial credit for files 500-1000 lines
302+ percentage_large = (len (large_files ) / total_files ) * 100
303+ if percentage_large < 5 :
304+ score = 90
305+ status = "pass"
306+ else :
307+ score = max (75 , 100 - (percentage_large * 5 ))
308+ status = "pass"
309+
310+ evidence = [
311+ f"Found { len (large_files )} files 500-1000 lines ({ percentage_large :.1f} % of { total_files } files)" ,
312+ ]
313+ else :
314+ # Perfect score
315+ score = 100.0
316+ status = "pass"
317+ evidence = [f"All { total_files } source files are <500 lines" ]
318+
319+ return Finding (
320+ attribute = self .attribute ,
321+ status = status ,
322+ score = score ,
323+ measured_value = f"{ len (huge_files )} huge, { len (large_files )} large out of { total_files } " ,
324+ threshold = "<5% files >500 lines, 0 files >1000 lines" ,
325+ evidence = evidence ,
326+ remediation = (
327+ None
328+ if status == "pass"
329+ else Remediation (
330+ summary = "Refactor large files into smaller, focused modules" ,
331+ steps = [
332+ "Identify files >1000 lines" ,
333+ "Split into logical submodules" ,
334+ "Extract classes/functions into separate files" ,
335+ "Maintain single responsibility principle" ,
336+ ],
337+ tools = ["refactoring tools" , "linters" ],
338+ commands = [],
339+ examples = [
340+ "# Split large file:\n # models.py (1500 lines) → models/user.py, models/product.py, models/order.py"
341+ ],
342+ citations = [],
343+ )
344+ ),
345+ error_message = None ,
346+ )
347+
348+
224349# Create stub assessors for remaining attributes
225350# These return "not_applicable" for now but can be enhanced later
226351
@@ -269,20 +394,6 @@ def create_stub_assessors():
269394 """Create stub assessors for remaining attributes."""
270395 return [
271396 # Tier 2 Critical
272- StubAssessor (
273- "one_command_setup" ,
274- "One-Command Build/Setup" ,
275- "Build & Development" ,
276- 2 ,
277- 0.03 ,
278- ),
279- StubAssessor (
280- "file_size_limits" ,
281- "File Size Limits" ,
282- "Context Window Optimization" ,
283- 2 ,
284- 0.03 ,
285- ),
286397 StubAssessor (
287398 "dependency_freshness" ,
288399 "Dependency Freshness & Security" ,
@@ -317,7 +428,7 @@ def create_stub_assessors():
317428 "Issue & Pull Request Templates" ,
318429 "Git & Version Control" ,
319430 4 ,
320- 0.01 ,
431+ 0.01
321432 ),
322433 StubAssessor (
323434 "container_setup" ,
0 commit comments