Skip to content

Commit fd2be32

Browse files
jwm4claude
andcommitted
fix: sort README candidates for determinism and guard iterdir OSErrors
- README scan: sort candidates before next() so selection is deterministic when multiple variants exist (e.g. readme.md and README.rst both present) - README scan: wrap iterdir() in try/except OSError, return Finding.error instead of crashing - ADR scan: wrap both docs/ and root iterdir() calls in try/except OSError, fall through gracefully rather than propagating Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 960e005 commit fd2be32

1 file changed

Lines changed: 36 additions & 19 deletions

File tree

src/agentready/assessors/documentation.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -401,15 +401,23 @@ def assess(self, repository: Repository) -> Finding:
401401
Scoring: Proportional based on section count
402402
"""
403403
# Case-insensitive README lookup — handles readme.md, README.md, Readme.rst, etc.
404+
# sorted() ensures deterministic selection when multiple variants exist.
404405
readme_names = {"readme.md", "readme.rst", "readme.txt", "readme"}
405-
readme_path = next(
406-
(
407-
f
408-
for f in repository.path.iterdir()
409-
if f.is_file() and f.name.lower() in readme_names
410-
),
411-
None,
412-
)
406+
try:
407+
readme_path = next(
408+
(
409+
f
410+
for f in sorted(
411+
repository.path.iterdir(), key=lambda f: f.name.lower()
412+
)
413+
if f.is_file() and f.name.lower() in readme_names
414+
),
415+
None,
416+
)
417+
except OSError as e:
418+
return Finding.error(
419+
self.attribute, reason=f"Could not scan repository root: {e}"
420+
)
413421

414422
if readme_path is None:
415423
return Finding(
@@ -573,23 +581,32 @@ def assess(self, repository: Repository) -> Finding:
573581
# Search docs/ first (most common location)
574582
docs_dir = repository.path / "docs"
575583
if docs_dir.is_dir():
576-
for candidate in sorted(docs_dir.iterdir()):
577-
if candidate.is_dir() and candidate.name.lower() in adr_target_names:
578-
adr_dir = candidate
579-
break
580-
581-
# Fall back to repo root and hidden .adr
582-
if not adr_dir:
583-
if (repository.path / ".adr").is_dir():
584-
adr_dir = repository.path / ".adr"
585-
else:
586-
for candidate in sorted(repository.path.iterdir()):
584+
try:
585+
for candidate in sorted(docs_dir.iterdir()):
587586
if (
588587
candidate.is_dir()
589588
and candidate.name.lower() in adr_target_names
590589
):
591590
adr_dir = candidate
592591
break
592+
except OSError:
593+
pass # docs/ unreadable — fall through to root scan
594+
595+
# Fall back to repo root and hidden .adr
596+
if not adr_dir:
597+
if (repository.path / ".adr").is_dir():
598+
adr_dir = repository.path / ".adr"
599+
else:
600+
try:
601+
for candidate in sorted(repository.path.iterdir()):
602+
if (
603+
candidate.is_dir()
604+
and candidate.name.lower() in adr_target_names
605+
):
606+
adr_dir = candidate
607+
break
608+
except OSError:
609+
pass # root unreadable — adr_dir stays None, fail finding follows
593610

594611
if not adr_dir:
595612
return Finding(

0 commit comments

Comments
 (0)