Skip to content

Commit e2a5778

Browse files
authored
fix(assessors): search recursively for OpenAPI specification files (#127)
Previously, OpenAPISpecsAssessor only checked the repository root directory for OpenAPI files. This caused false negatives when OpenAPI specs were located in subdirectories (e.g., api/v1alpha1/openapi.yaml). Changes: - Use rglob() to recursively search for OpenAPI files - Filter out common build/dependency directories (.git, node_modules, etc.) - Prefer root-level specs when multiple files are found - Update evidence messages to show relative paths and indicate multiple files
1 parent 90a3e58 commit e2a5778

1 file changed

Lines changed: 45 additions & 10 deletions

File tree

src/agentready/assessors/documentation.py

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)