mirror of
https://github.com/efremidze/swift-architecture-skill.git
synced 2026-09-14 20:49:06 +08:00
294d8ee0b1
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
179 lines
5.5 KiB
Python
Executable File
179 lines
5.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Dict, List, Set, Tuple
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[3]
|
|
REFERENCES_DIR = ROOT / "swift-architecture-skill" / "references"
|
|
SKILL_PATH = ROOT / "swift-architecture-skill" / "SKILL.md"
|
|
SELECTION_GUIDE_PATH = REFERENCES_DIR / "selection-guide.md"
|
|
README_PATH = ROOT / "README.md"
|
|
QUALITY_CONTRACT_PATH = ROOT / "tooling" / "evals" / "contract.json"
|
|
|
|
|
|
SELECTION_HEADER_TO_SLUG: Dict[str, str] = {
|
|
"MVVM": "mvvm",
|
|
"MVI": "mvi",
|
|
"TCA": "tca",
|
|
"Clean": "clean-architecture",
|
|
"VIPER": "viper",
|
|
"Reactive": "reactive",
|
|
"MVP": "mvp",
|
|
"Coordinator": "coordinator",
|
|
}
|
|
|
|
README_NAME_TO_SLUG: Dict[str, str] = {
|
|
"MVVM": "mvvm",
|
|
"MVI": "mvi",
|
|
"TCA": "tca",
|
|
"Clean Architecture": "clean-architecture",
|
|
"VIPER": "viper",
|
|
"Reactive": "reactive",
|
|
"MVP": "mvp",
|
|
"Coordinator": "coordinator",
|
|
}
|
|
|
|
|
|
def read_text(path: Path) -> str:
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
|
|
def expected_reference_slugs() -> Set[str]:
|
|
slugs = {
|
|
p.stem
|
|
for p in REFERENCES_DIR.glob("*.md")
|
|
if p.stem not in {"selection-guide", "_index"}
|
|
}
|
|
return slugs
|
|
|
|
|
|
def parse_skill_mapping_slugs(skill_text: str) -> Set[str]:
|
|
pattern = re.compile(
|
|
r"^- [^`]+?\s*(?:→|->)\s*`references/([a-z0-9-]+)\.md`$",
|
|
flags=re.MULTILINE,
|
|
)
|
|
return set(pattern.findall(skill_text))
|
|
|
|
|
|
def parse_selection_matrix_slugs(selection_text: str) -> Tuple[Set[str], List[str]]:
|
|
errors: List[str] = []
|
|
matrix_line = None
|
|
for line in selection_text.splitlines():
|
|
if line.startswith("| Factor |"):
|
|
matrix_line = line
|
|
break
|
|
|
|
if matrix_line is None:
|
|
return set(), ["selection-guide.md: decision matrix header line not found"]
|
|
|
|
parts = [part.strip() for part in matrix_line.split("|") if part.strip()]
|
|
if len(parts) < 2:
|
|
return set(), ["selection-guide.md: invalid decision matrix header format"]
|
|
|
|
header_slugs: Set[str] = set()
|
|
for header in parts[1:]:
|
|
if header not in SELECTION_HEADER_TO_SLUG:
|
|
errors.append(f"selection-guide.md: unknown matrix header '{header}'")
|
|
continue
|
|
header_slugs.add(SELECTION_HEADER_TO_SLUG[header])
|
|
|
|
return header_slugs, errors
|
|
|
|
|
|
def parse_selection_flow_slugs(selection_text: str) -> Set[str]:
|
|
return set(re.findall(r"references/([a-z0-9-]+)\.md", selection_text))
|
|
|
|
|
|
def parse_readme_supported_slugs(readme_text: str) -> Tuple[Set[str], List[str]]:
|
|
errors: List[str] = []
|
|
slugs: Set[str] = set()
|
|
|
|
# Format 1: bullet list - **Name** - description
|
|
matches = re.findall(r"^- \*\*(.+?)\*\* -", readme_text, flags=re.MULTILINE)
|
|
|
|
# Format 2: inline dot-separated list MVP · MVVM · Clean Architecture · ...
|
|
if not matches:
|
|
for line in readme_text.splitlines():
|
|
if " · " in line:
|
|
parts = [p.strip() for p in line.split(" · ")]
|
|
if all(p in README_NAME_TO_SLUG for p in parts):
|
|
matches = parts
|
|
break
|
|
|
|
unknown_named = sorted(set(matches) - set(README_NAME_TO_SLUG.keys()))
|
|
if unknown_named:
|
|
errors.append(
|
|
"README.md: unknown supported architecture entries: "
|
|
+ ", ".join(unknown_named)
|
|
)
|
|
for name in matches:
|
|
if name not in README_NAME_TO_SLUG:
|
|
continue
|
|
slugs.add(README_NAME_TO_SLUG[name])
|
|
|
|
missing_named = set(README_NAME_TO_SLUG.keys()) - set(matches)
|
|
if missing_named:
|
|
errors.append(
|
|
"README.md: missing supported architecture entries for "
|
|
+ ", ".join(sorted(missing_named))
|
|
)
|
|
|
|
return slugs, errors
|
|
|
|
|
|
def parse_quality_contract_slugs(contract_text: str) -> Set[str]:
|
|
return set(
|
|
re.findall(
|
|
r"swift-architecture-skill/references/([a-z0-9-]+)\.md",
|
|
contract_text,
|
|
)
|
|
)
|
|
|
|
|
|
def compare_sets(label: str, actual: Set[str], expected: Set[str]) -> List[str]:
|
|
errors: List[str] = []
|
|
missing = expected - actual
|
|
extra = actual - expected
|
|
if missing:
|
|
errors.append(f"{label}: missing {sorted(missing)}")
|
|
if extra:
|
|
errors.append(f"{label}: unexpected {sorted(extra)}")
|
|
return errors
|
|
|
|
|
|
def main() -> int:
|
|
expected = expected_reference_slugs()
|
|
errors: List[str] = []
|
|
|
|
skill_mapping = parse_skill_mapping_slugs(read_text(SKILL_PATH))
|
|
errors.extend(compare_sets("SKILL.md architecture mapping", skill_mapping, expected))
|
|
|
|
selection_text = read_text(SELECTION_GUIDE_PATH)
|
|
matrix_slugs, matrix_errors = parse_selection_matrix_slugs(selection_text)
|
|
errors.extend(matrix_errors)
|
|
errors.extend(compare_sets("selection-guide matrix headers", matrix_slugs, expected))
|
|
|
|
flow_slugs = parse_selection_flow_slugs(selection_text)
|
|
errors.extend(compare_sets("selection-guide decision flow references", flow_slugs, expected))
|
|
|
|
readme_slugs, readme_errors = parse_readme_supported_slugs(read_text(README_PATH))
|
|
errors.extend(readme_errors)
|
|
errors.extend(compare_sets("README supported architectures", readme_slugs, expected))
|
|
|
|
contract_slugs = parse_quality_contract_slugs(read_text(QUALITY_CONTRACT_PATH))
|
|
errors.extend(compare_sets("testing quality contract playbooks", contract_slugs, expected))
|
|
|
|
if errors:
|
|
print("Architecture consistency validation failed:")
|
|
for error in errors:
|
|
print(f"- {error}")
|
|
return 1
|
|
|
|
print("Architecture consistency validation passed.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|