diff --git a/src/agentready/assessors/documentation.py b/src/agentready/assessors/documentation.py index 2d4240ca..7dc153a1 100644 --- a/src/agentready/assessors/documentation.py +++ b/src/agentready/assessors/documentation.py @@ -400,9 +400,37 @@ def assess(self, repository: Repository) -> Finding: Pass criteria: README.md exists with essential sections Scoring: Proportional based on section count """ - readme_path = repository.path / "README.md" + # Case-insensitive README lookup — handles readme.md, README.md, Readme.rst, etc. + # sorted() ensures deterministic selection when multiple variants exist. + readme_names = {"readme.md", "readme.rst", "readme.txt", "readme"} + try: + readme_path = next( + ( + f + for f in sorted( + repository.path.iterdir(), key=lambda f: f.name.lower() + ) + if f.is_file() and f.name.lower() in readme_names + ), + None, + ) + except OSError as e: + return Finding.error( + self.attribute, reason=f"Could not scan repository root: {e}" + ) + + if readme_path is None: + return Finding( + attribute=self.attribute, + status="fail", + score=0.0, + measured_value="missing", + threshold="present with sections", + evidence=["README not found"], + remediation=self._create_remediation(), + error_message=None, + ) - # Fix TOCTOU: Use try-except around file read instead of existence check try: with open(readme_path, "r", encoding="utf-8") as f: content = f.read().lower() @@ -450,20 +478,9 @@ def assess(self, repository: Repository) -> Finding: error_message=None, ) - except FileNotFoundError: - return Finding( - attribute=self.attribute, - status="fail", - score=0.0, - measured_value="missing", - threshold="present with sections", - evidence=["README.md not found"], - remediation=self._create_remediation(), - error_message=None, - ) except OSError as e: return Finding.error( - self.attribute, reason=f"Could not read README.md: {str(e)}" + self.attribute, reason=f"Could not read {readme_path.name}: {str(e)}" ) def _create_remediation(self) -> Remediation: @@ -549,23 +566,47 @@ def assess(self, repository: Repository) -> Finding: - ADR count (40%, up to 5 ADRs) - Template compliance (20%) """ - # Check for ADR directory in common locations - adr_paths = [ - repository.path / "docs" / "adr", - repository.path / ".adr", - repository.path / "adr", - repository.path / "docs" / "decisions", - repository.path / "specs", - repository.path / "docs" / "specs", - repository.path / "docs" / "architecture", - repository.path / "docs" / "design", - ] - + # Case-insensitive ADR directory scan — handles docs/adr, docs/ADRs, docs/Adr, adr/, etc. + adr_target_names = { + "adr", + "adrs", + "decisions", + "architecture-decisions", + "architecture", + "design", + "specs", + } adr_dir = None - for path in adr_paths: - if path.exists() and path.is_dir(): - adr_dir = path - break + + # Search docs/ first (most common location) + docs_dir = repository.path / "docs" + if docs_dir.is_dir(): + try: + for candidate in sorted(docs_dir.iterdir()): + if ( + candidate.is_dir() + and candidate.name.lower() in adr_target_names + ): + adr_dir = candidate + break + except OSError: + pass # docs/ unreadable — fall through to root scan + + # Fall back to repo root and hidden .adr + if not adr_dir: + if (repository.path / ".adr").is_dir(): + adr_dir = repository.path / ".adr" + else: + try: + for candidate in sorted(repository.path.iterdir()): + if ( + candidate.is_dir() + and candidate.name.lower() in adr_target_names + ): + adr_dir = candidate + break + except OSError: + pass # root unreadable — adr_dir stays None, fail finding follows if not adr_dir: return Finding( @@ -575,7 +616,7 @@ def assess(self, repository: Repository) -> Finding: measured_value="no ADR directory", threshold="ADR directory with decisions", evidence=[ - "No ADR directory found (checked docs/adr/, .adr/, adr/, docs/decisions/, specs/, docs/specs/, docs/architecture/, docs/design/)" + "No ADR directory found (checked docs/adr/, docs/architecture/, docs/specs/, specs/, adr/, and variants — all case-insensitive)" ], remediation=self._create_remediation(), error_message=None, diff --git a/src/agentready/assessors/structure.py b/src/agentready/assessors/structure.py index 6409ecf4..bfc59fef 100644 --- a/src/agentready/assessors/structure.py +++ b/src/agentready/assessors/structure.py @@ -531,6 +531,8 @@ def _check_setup_files(self, repository: Repository) -> list: # Check for common setup files files_to_check = { "Makefile": "Makefile", + "GNUmakefile": "GNUmakefile", + "makefile": "Makefile", "setup.sh": "shell script", "bootstrap.sh": "bootstrap script", "package.json": "npm/yarn",