@@ -400,9 +400,29 @@ def assess(self, repository: Repository) -> Finding:
400400 Pass criteria: README.md exists with essential sections
401401 Scoring: Proportional based on section count
402402 """
403- readme_path = repository .path / "README.md"
403+ # Case-insensitive README lookup — handles readme.md, README.md, Readme.rst, etc.
404+ 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+ )
413+
414+ if readme_path is None :
415+ return Finding (
416+ attribute = self .attribute ,
417+ status = "fail" ,
418+ score = 0.0 ,
419+ measured_value = "missing" ,
420+ threshold = "present with sections" ,
421+ evidence = ["README not found" ],
422+ remediation = self ._create_remediation (),
423+ error_message = None ,
424+ )
404425
405- # Fix TOCTOU: Use try-except around file read instead of existence check
406426 try :
407427 with open (readme_path , "r" , encoding = "utf-8" ) as f :
408428 content = f .read ().lower ()
@@ -450,20 +470,9 @@ def assess(self, repository: Repository) -> Finding:
450470 error_message = None ,
451471 )
452472
453- except FileNotFoundError :
454- return Finding (
455- attribute = self .attribute ,
456- status = "fail" ,
457- score = 0.0 ,
458- measured_value = "missing" ,
459- threshold = "present with sections" ,
460- evidence = ["README.md not found" ],
461- remediation = self ._create_remediation (),
462- error_message = None ,
463- )
464473 except OSError as e :
465474 return Finding .error (
466- self .attribute , reason = f"Could not read README.md : { str (e )} "
475+ self .attribute , reason = f"Could not read { readme_path . name } : { str (e )} "
467476 )
468477
469478 def _create_remediation (self ) -> Remediation :
@@ -551,23 +560,33 @@ def assess(self, repository: Repository) -> Finding:
551560 - ADR count (40%, up to 5 ADRs)
552561 - Template compliance (20%)
553562 """
554- # Check for ADR directory in common locations
555- adr_paths = [
556- repository .path / "docs" / "adr" ,
557- repository .path / ".adr" ,
558- repository .path / "adr" ,
559- repository .path / "docs" / "decisions" ,
560- repository .path / "specs" ,
561- repository .path / "docs" / "specs" ,
562- repository .path / "docs" / "architecture" ,
563- repository .path / "docs" / "design" ,
564- ]
565-
563+ # Case-insensitive ADR directory scan — handles docs/adr, docs/ADRs, docs/Adr, adr/, etc.
564+ adr_target_names = {
565+ "adr" , "adrs" , "decisions" , "architecture-decisions" ,
566+ "specs" , "architecture" , "design" ,
567+ }
566568 adr_dir = None
567- for path in adr_paths :
568- if path .exists () and path .is_dir ():
569- adr_dir = path
570- break
569+
570+ # Search docs/ first (most common location)
571+ docs_dir = repository .path / "docs"
572+ if docs_dir .is_dir ():
573+ for candidate in sorted (docs_dir .iterdir ()):
574+ if candidate .is_dir () and candidate .name .lower () in adr_target_names :
575+ adr_dir = candidate
576+ break
577+
578+ # Fall back to repo root and hidden .adr
579+ if not adr_dir :
580+ if (repository .path / ".adr" ).is_dir ():
581+ adr_dir = repository .path / ".adr"
582+ else :
583+ for candidate in sorted (repository .path .iterdir ()):
584+ if (
585+ candidate .is_dir ()
586+ and candidate .name .lower () in adr_target_names
587+ ):
588+ adr_dir = candidate
589+ break
571590
572591 if not adr_dir :
573592 return Finding (
@@ -577,7 +596,7 @@ def assess(self, repository: Repository) -> Finding:
577596 measured_value = "no ADR directory" ,
578597 threshold = "ADR directory with decisions" ,
579598 evidence = [
580- "No ADR directory found (checked docs/adr/, .adr/, adr/, docs/decisions /, specs /, docs/ specs/, docs/ architecture/, docs/ design/)"
599+ "No ADR directory found (checked docs/adr/, docs/ADRs /, adr /, specs/, architecture/, design/, and variants )"
581600 ],
582601 remediation = self ._create_remediation (),
583602 error_message = None ,
0 commit comments