From fc32922c441ee7c0f7a67c752d790b08e1a72bae Mon Sep 17 00:00:00 2001 From: EnragedAntelope Date: Sat, 31 Oct 2026 15:24:10 -0400 Subject: [PATCH] Fix on-demand wildcard path resolution and metadata scanning - Resolve normalized wildcard keys through indexed original paths - Prevent recursive self-matches for missing indexed files - Exclude Git metadata consistently from wildcard scans - Cover path lookup, loading modes and precedence regressions --- modules/impact/wildcards.py | 13 +++- tests/wildcards/test_populate_regression.py | 82 +++++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/modules/impact/wildcards.py b/modules/impact/wildcards.py index 05d6530c..c1adbdf3 100644 --- a/modules/impact/wildcards.py +++ b/modules/impact/wildcards.py @@ -111,6 +111,7 @@ def calculate_directory_size(directory_path, limit=None): total_size = 0 try: for root, directories, files in os.walk(directory_path, followlinks=True): + directories[:] = [d for d in directories if d not in ('.git', '.github')] for file in files: if file.endswith(('.txt', '.yaml', '.yml')): file_path = os.path.join(root, file) @@ -145,6 +146,7 @@ def scan_wildcard_metadata(wildcard_path): discovered = 0 try: for root, directories, files in os.walk(wildcard_path, followlinks=True): + directories[:] = [d for d in directories if d not in ('.git', '.github')] for file in files: if file.endswith('.txt'): file_path = os.path.join(root, file) @@ -243,6 +245,12 @@ def find_wildcard_file(key): except Exception: pass + # Normalization changes spaces and case; keep the original indexed path. + # Preserve direct-file precedence before falling back to the metadata. + file_path = available_wildcards.get(key) + if file_path and os.path.isfile(file_path): + return (file_path, file_path.endswith(('.yaml', '.yml'))) + # Case 2: YAML nested key (e.g., "colors/warm" → "colors.yaml") if '/' in key: parent_key = key.split('/')[0] @@ -297,8 +305,7 @@ def get_wildcard_value(key): # Example: "dragon" matches "dragon.txt", "fantasy/dragon.txt", "dragon/fire.txt", etc. matched_keys = [] for k in available_wildcards.keys(): - if (k == key or - k.endswith('/' + key) or + if k != key and (k.endswith('/' + key) or k.startswith(key + '/') or ('/' + key + '/') in k): matched_keys.append(k) @@ -453,6 +460,7 @@ def read_wildcard_dict(wildcard_path, on_demand=False): """ global wildcard_dict for root, directories, files in os.walk(wildcard_path, followlinks=True): + directories[:] = [d for d in directories if d not in ('.git', '.github')] for file in files: file_path = os.path.join(root, file) try: @@ -1120,6 +1128,7 @@ def load_yaml_files_only(wildcard_path): yaml_count = 0 try: for root, directories, files in os.walk(wildcard_path, followlinks=True): + directories[:] = [d for d in directories if d not in ('.git', '.github')] for file in files: if file.endswith('.yaml') or file.endswith('.yml'): file_path = os.path.join(root, file) diff --git a/tests/wildcards/test_populate_regression.py b/tests/wildcards/test_populate_regression.py index 7941cec7..f829afdb 100644 --- a/tests/wildcards/test_populate_regression.py +++ b/tests/wildcards/test_populate_regression.py @@ -24,6 +24,88 @@ from populate_helpers import ( # noqa: E402 SEEDS = [0, 1, 42, 100, 999, 12345, 2_147_483_647] +@pytest.fixture +def wildcard_directory(tmp_path, monkeypatch): + monkeypatch.setattr(wc, "wildcards_path", str(tmp_path)) + monkeypatch.setattr(wc, "wildcard_dict", {}) + monkeypatch.setattr(wc, "available_wildcards", {}) + monkeypatch.setattr(wc, "loaded_wildcards", {}) + monkeypatch.setattr(wc, "_on_demand_mode", True) + monkeypatch.setattr(wc.config, "get_config", dict) + return tmp_path + + +@pytest.mark.parametrize("name", ["my colors.txt", "colors.TXT.txt"]) +@pytest.mark.parametrize("prefix", ["", "collection/"]) +@pytest.mark.parametrize("short_name", [False, True]) +def test_on_demand_expands_indexed_normalized_paths(wildcard_directory, name, prefix, short_name): + path = wildcard_directory / prefix / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("red\n", encoding="utf-8") + wc.scan_wildcard_metadata(str(wildcard_directory)) + key = wc.wildcard_normalize(os.path.splitext(name if short_name else prefix + name)[0]) + + assert wc.process(f"__{key}__", 42) == "red" + + +@pytest.mark.parametrize("key", ["missing", "collection/missing"]) +def test_on_demand_missing_indexed_file_does_not_recurse(wildcard_directory, key): + wc.available_wildcards[key] = str(wildcard_directory / "removed.txt") + + assert wc.get_wildcard_value(key) is None + + +@pytest.mark.parametrize("on_demand", [False, True]) +def test_loading_preserves_direct_file_precedence(wildcard_directory, monkeypatch, on_demand): + custom = wildcard_directory / "custom" + custom.mkdir() + (wildcard_directory / "shared.txt").write_text("builtin\n", encoding="utf-8") + (custom / "shared.txt").write_text("custom\n", encoding="utf-8") + monkeypatch.setattr(wc.config, "get_config", lambda: {"custom_wildcards": str(custom)}) + monkeypatch.setattr(wc, "get_cache_limit", lambda: 1 if on_demand else 1024 * 1024) + + wc.wildcard_load() + + # Preserve the existing precedence in each mode, rather than changing it here. + assert wc.process("__shared__", 42) == ("builtin" if on_demand else "custom") + + +def test_on_demand_depth_match_keeps_all_available_options(wildcard_directory): + for directory, option in (("first", "red"), ("second/nested", "blue")): + path = wildcard_directory / directory / "my colors.txt" + path.parent.mkdir(parents=True) + path.write_text(option + "\n", encoding="utf-8") + wc.scan_wildcard_metadata(str(wildcard_directory)) + wc.available_wildcards["removed/my-colors"] = str(wildcard_directory / "removed.txt") + + assert sorted(wc.get_wildcard_value("my-colors")) == ["blue", "red"] + + +@pytest.mark.parametrize("on_demand", [False, True]) +def test_wildcard_scans_ignore_git_metadata(wildcard_directory, monkeypatch, caplog, on_demand): + for name in (".git", ".github"): + metadata = wildcard_directory / "collection" / name / "workflows" + metadata.mkdir(parents=True) + (metadata / "publish.yml").write_text("on: [push]\n", encoding="utf-8") + (metadata / "internal.txt").write_text("not a wildcard\n", encoding="utf-8") + # Other dot-directories remain valid wildcard locations. + visible = wildcard_directory / ".styles" + visible.mkdir() + (visible / "colors.txt").write_text("red\n", encoding="utf-8") + (wildcard_directory / "colors.yaml").write_text("palette: [blue]\n", encoding="utf-8") + expected_size = sum(p.stat().st_size for p in + (visible / "colors.txt", wildcard_directory / "colors.yaml")) + monkeypatch.setattr(wc, "get_cache_limit", lambda: 1 if on_demand else 1024 * 1024) + + wc.wildcard_load() + + assert wc.calculate_directory_size(str(wildcard_directory)) == expected_size + assert wc.process("__.styles/colors__ __palette__", 42) == "red blue" + keys = set(wc.available_wildcards) | set(wc.wildcard_dict) | set(wc.loaded_wildcards) + assert not any("/.git/" in k or "/.github/" in k for k in keys) + assert not [r for r in caplog.records if r.levelno >= logging.WARNING] + + # ============================================================================ # Category 1: Basic wildcard __x__ # Tutorial L28 / PRD §2.1, §2.5