@@ -1138,13 +1138,38 @@ def assess(self, repository: Repository) -> Finding:
11381138 "swagger.json" ,
11391139 ]
11401140
1141- # Check for spec file
1142- found_spec = None
1141+ # Recursively search for spec files
1142+ found_specs = []
1143+ excluded_dirs = {".git" , "node_modules" , ".venv" , "venv" , "__pycache__" , ".pytest_cache" }
1144+
11431145 for spec_name in spec_files :
1144- spec_path = repository .path / spec_name
1145- if spec_path .exists ():
1146- found_spec = spec_path
1147- break
1146+ try :
1147+ # Use rglob to search recursively
1148+ matches = list (repository .path .rglob (spec_name ))
1149+ # Filter out files in excluded directories
1150+ matches = [
1151+ m for m in matches
1152+ if not any (part in m .parts for part in excluded_dirs )
1153+ ]
1154+ found_specs .extend (matches )
1155+ except OSError :
1156+ # If rglob fails, continue to next pattern
1157+ continue
1158+
1159+ # Remove duplicates while preserving order
1160+ seen = set ()
1161+ unique_specs = []
1162+ for spec in found_specs :
1163+ if spec not in seen :
1164+ seen .add (spec )
1165+ unique_specs .append (spec )
1166+
1167+ # Select the first found spec (prefer root-level if available, otherwise first found)
1168+ found_spec = None
1169+ if unique_specs :
1170+ # Prefer root-level specs, otherwise use first found
1171+ root_specs = [s for s in unique_specs if s .parent == repository .path ]
1172+ found_spec = root_specs [0 ] if root_specs else unique_specs [0 ]
11481173
11491174 if not found_spec :
11501175 return Finding (
@@ -1155,7 +1180,7 @@ def assess(self, repository: Repository) -> Finding:
11551180 threshold = "OpenAPI 3.x spec present" ,
11561181 evidence = [
11571182 "No OpenAPI specification found" ,
1158- f"Searched: { ', ' .join (spec_files )} " ,
1183+ f"Searched recursively for : { ', ' .join (spec_files )} " ,
11591184 ],
11601185 remediation = self ._create_remediation (),
11611186 error_message = None ,
@@ -1172,9 +1197,10 @@ def assess(self, repository: Repository) -> Finding:
11721197 try :
11731198 spec_data = json .loads (content )
11741199 except json .JSONDecodeError as e :
1200+ spec_relative_path = found_spec .relative_to (repository .path )
11751201 return Finding .error (
11761202 self .attribute ,
1177- reason = f"Could not parse { found_spec . name } : { str (e )} " ,
1203+ reason = f"Could not parse { spec_relative_path } : { str (e )} " ,
11781204 )
11791205
11801206 # Extract version and check completeness
@@ -1208,7 +1234,15 @@ def assess(self, repository: Repository) -> Finding:
12081234 status = "pass" if total_score >= 75 else "fail"
12091235
12101236 # Build evidence
1211- evidence = [f"{ found_spec .name } found in repository" ]
1237+ spec_relative_path = found_spec .relative_to (repository .path )
1238+ evidence = [f"{ spec_relative_path } found in repository" ]
1239+
1240+ # Indicate if multiple OpenAPI files were found
1241+ if len (unique_specs ) > 1 :
1242+ other_specs = [s .relative_to (repository .path ) for s in unique_specs if s != found_spec ]
1243+ evidence .append (f"Additional OpenAPI files found: { ', ' .join (str (p ) for p in other_specs [:3 ])} " )
1244+ if len (other_specs ) > 3 :
1245+ evidence .append (f"... and { len (other_specs ) - 3 } more" )
12121246
12131247 if openapi_version :
12141248 evidence .append (f"OpenAPI version: { openapi_version } " )
@@ -1238,8 +1272,9 @@ def assess(self, repository: Repository) -> Finding:
12381272 )
12391273
12401274 except (OSError , UnicodeDecodeError ) as e :
1275+ spec_relative_path = found_spec .relative_to (repository .path )
12411276 return Finding .error (
1242- self .attribute , reason = f"Could not read { found_spec . name } : { str (e )} "
1277+ self .attribute , reason = f"Could not read { spec_relative_path } : { str (e )} "
12431278 )
12441279
12451280 def _create_remediation (self ) -> Remediation :
0 commit comments