diff --git a/scripts/verify_static_analysis_policy.py b/scripts/verify_static_analysis_policy.py index 14154b7..c68f6eb 100644 --- a/scripts/verify_static_analysis_policy.py +++ b/scripts/verify_static_analysis_policy.py @@ -89,10 +89,32 @@ def _excluded_path_values(policy: Mapping[str, Any]) -> tuple[str, ...]: ) +def _tracked_python_files(repo_root: Path) -> frozenset[str] | None: + result = subprocess.run( + ["git", "ls-files", "--cached", "--", "*.py"], + cwd=repo_root, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + check=False, + shell=False, + ) + if result.returncode != 0: + return None + # IMPORTANT: governance ownership must not include ignored maintainer-local files. + return frozenset( + line.strip().replace("\\", "/") + for line in result.stdout.splitlines() + if line.strip() + ) + + def discover_owned_python_files( repo_root: Path, policy: Mapping[str, Any] ) -> tuple[str, ...]: excluded = _excluded_path_values(policy) + tracked_files = _tracked_python_files(repo_root) discovered: set[str] = set() for root_value in policy.get("production_roots", []): root_path = repo_root / str(root_value) @@ -105,6 +127,8 @@ def discover_owned_python_files( continue for candidate in candidates: relative = _repo_relative_path(candidate, repo_root) + if tracked_files is not None and relative not in tracked_files: + continue if any(_path_within(relative, excluded_path) for excluded_path in excluded): continue discovered.add(relative) diff --git a/tests/static_analysis_policy.json b/tests/static_analysis_policy.json index b5f7741..78c4c15 100644 --- a/tests/static_analysis_policy.json +++ b/tests/static_analysis_policy.json @@ -4354,27 +4354,6 @@ "message": "Use `contextlib.suppress(KeyboardInterrupt)` instead of `try`-`except`-`pass`", "count": 1 }, - { - "tool": "ruff", - "path": "scripts/sync_split.py", - "code": "C416", - "message": "Unnecessary dict comprehension (rewrite using `dict()`)", - "count": 1 - }, - { - "tool": "ruff", - "path": "scripts/sync_split.py", - "code": "I001", - "message": "Import block is un-sorted or un-formatted", - "count": 1 - }, - { - "tool": "ruff", - "path": "scripts/sync_split.py", - "code": "UP035", - "message": "Import from `collections.abc` instead: `Iterable`", - "count": 1 - }, { "tool": "ruff", "path": "scripts/verify_exception_boundary_policy.py", diff --git a/tests/test_r217_static_analysis_policy.py b/tests/test_r217_static_analysis_policy.py index 2b7124a..f932fe9 100644 --- a/tests/test_r217_static_analysis_policy.py +++ b/tests/test_r217_static_analysis_policy.py @@ -1,4 +1,5 @@ import json +import subprocess import sys import tempfile import unittest @@ -88,6 +89,48 @@ class TestStaticAnalysisPolicy(unittest.TestCase): ), ) + def test_git_worktree_discovery_excludes_ignored_and_untracked_python(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self._create_repo(root) + (root / ".gitignore").write_text("pkg/local_ignored.py\n", encoding="utf-8") + (root / "pkg" / "local_ignored.py").write_text( + "IGNORED = True\n", encoding="utf-8" + ) + (root / "pkg" / "local_untracked.py").write_text( + "UNTRACKED = True\n", encoding="utf-8" + ) + subprocess.run( + ["git", "init", "--quiet"], cwd=root, check=True, capture_output=True + ) + subprocess.run( + [ + "git", + "add", + ".gitignore", + "config.py", + "pkg/owned.py", + "pkg/clean.py", + "pkg/generated.py", + "pkg/nested/child.py", + ], + cwd=root, + check=True, + capture_output=True, + ) + + files = policy_module.discover_owned_python_files(root, self._policy()) + + self.assertEqual( + files, + ( + "config.py", + "pkg/clean.py", + "pkg/nested/child.py", + "pkg/owned.py", + ), + ) + def test_exact_baseline_passes(self): policy = self._policy() current = Counter(