diff --git a/src/devx/tools/check_agent_docs.py b/src/devx/tools/check_agent_docs.py index 19c2ae0..95472fb 100644 --- a/src/devx/tools/check_agent_docs.py +++ b/src/devx/tools/check_agent_docs.py @@ -117,6 +117,7 @@ def _check_file( legitimate_indicators: list[str], repo_path_prefixes: list[str], min_path_ref_length: int, + skip_ref_prefixes: list[str], ) -> list[str]: """Check a single file for stale references.""" issues: list[str] = [] @@ -147,6 +148,9 @@ def _check_file( # Only check references that look like repo paths if not any(ref.startswith(prefix) for prefix in repo_path_prefixes): continue + # Skip references matching configured skip prefixes (e.g. aspirational test files) + if any(ref.startswith(prefix) for prefix in skip_ref_prefixes): + continue candidate = repo_root / ref if not candidate.exists(): issues.append(f"{rel_path}:{lineno}: references non-existent file '{ref}'") @@ -179,6 +183,9 @@ def cli() -> None: min_len_raw = cfg.get("min_path_ref_length") min_path_ref_length: int = int(min_len_raw) if isinstance(min_len_raw, int) else MIN_PATH_REF_LENGTH_DEFAULT + skip_prefixes_raw = cfg.get("skip_ref_prefixes", []) + skip_ref_prefixes: list[str] = [str(d) for d in skip_prefixes_raw] if isinstance(skip_prefixes_raw, list) else [] + deleted_files: set[str] = set() deleted_raw = cfg.get("deleted_files", []) if isinstance(deleted_raw, list): @@ -209,6 +216,7 @@ def cli() -> None: legitimate_indicators, repo_path_prefixes, min_path_ref_length, + skip_ref_prefixes, ) all_issues.extend(issues) diff --git a/tests/unit/test_check_agent_docs.py b/tests/unit/test_check_agent_docs.py index 2080acd..c5dc177 100644 --- a/tests/unit/test_check_agent_docs.py +++ b/tests/unit/test_check_agent_docs.py @@ -89,7 +89,7 @@ class TestCheckFile: doc.parent.mkdir(parents=True) doc.write_text("See scripts/old.py for details.\n") issues = _check_file( - doc, tmp_path, {"scripts/old.py"}, [], [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT + doc, tmp_path, {"scripts/old.py"}, [], [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT, [] ) assert any("deleted file" in i for i in issues) @@ -97,7 +97,7 @@ class TestCheckFile: doc = tmp_path / "docs" / "guide.md" doc.parent.mkdir(parents=True) doc.write_text("See scripts/nonexistent.py for details.\n") - issues = _check_file(doc, tmp_path, set(), [], [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT) + issues = _check_file(doc, tmp_path, set(), [], [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT, []) assert any("non-existent file" in i for i in issues) def test_does_not_flag_existing_file(self, tmp_path: Path) -> None: @@ -106,7 +106,7 @@ class TestCheckFile: doc = tmp_path / "docs" / "guide.md" doc.parent.mkdir(parents=True) doc.write_text("See scripts/exists.py for details.\n") - issues = _check_file(doc, tmp_path, set(), [], [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT) + issues = _check_file(doc, tmp_path, set(), [], [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT, []) assert issues == [] def test_detects_deprecated_pattern(self, tmp_path: Path) -> None: @@ -115,7 +115,7 @@ class TestCheckFile: doc.write_text("Use ansible/envs/prod/secrets.yml for config.\n") patterns = [re.compile(r"ansible/envs/[^/]+/secrets\.yml")] issues = _check_file( - doc, tmp_path, set(), patterns, [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT + doc, tmp_path, set(), patterns, [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT, [] ) assert any("deprecated pattern" in i for i in issues) @@ -129,7 +129,7 @@ class TestCheckFile: doc.write_text("The legacy ansible/envs/prod/secrets.yml is deprecated.\n") patterns = [re.compile(r"ansible/envs/[^/]+/secrets\.yml")] issues = _check_file( - doc, tmp_path, set(), patterns, ["deprecated"], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT + doc, tmp_path, set(), patterns, ["deprecated"], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT, [] ) assert issues == [] @@ -137,14 +137,14 @@ class TestCheckFile: doc = tmp_path / "docs" / "guide.md" doc.parent.mkdir(parents=True) doc.write_bytes(b"\xff\xfe\x00\x00") - issues = _check_file(doc, tmp_path, set(), [], [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT) + issues = _check_file(doc, tmp_path, set(), [], [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT, []) assert issues == [] def test_skips_short_ref(self, tmp_path: Path) -> None: doc = tmp_path / "docs" / "guide.md" doc.parent.mkdir(parents=True) doc.write_text("See a.py for details.\n") - issues = _check_file(doc, tmp_path, set(), [], [], DEFAULT_REPO_PATH_PREFIXES, 5) + issues = _check_file(doc, tmp_path, set(), [], [], DEFAULT_REPO_PATH_PREFIXES, 5, []) # "a.py" is only 4 chars, below min_path_ref_length assert issues == [] @@ -152,10 +152,19 @@ class TestCheckFile: doc = tmp_path / "docs" / "guide.md" doc.parent.mkdir(parents=True) doc.write_text("See vendor/some/long/path.py for details.\n") - issues = _check_file(doc, tmp_path, set(), [], [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT) + issues = _check_file(doc, tmp_path, set(), [], [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT, []) # "vendor/" is not in repo_path_prefixes assert issues == [] + def test_skip_ref_prefixes_skips_nonexistent(self, tmp_path: Path) -> None: + doc = tmp_path / "docs" / "guide.md" + doc.parent.mkdir(parents=True) + doc.write_text("See scripts/test_foo.py for details.\n") + issues = _check_file( + doc, tmp_path, set(), [], [], DEFAULT_REPO_PATH_PREFIXES, MIN_PATH_REF_LENGTH_DEFAULT, ["scripts/test_"] + ) + assert issues == [] + class TestCli: def test_passes_when_no_issues(self, tmp_path: Path) -> None: