- Declare `__all__` in every package `__init__.py` whose `PROJECT_ROOT`
is imported by its modules (163 packages), so the constant counts as
exported; list every public name where the package defines more.
- Empty the 15 package `__init__.py` files whose `PROJECT_ROOT` no module
imports.
- Declare the public API of `utils.github.variant.axes` in `__all__`,
re-exports included, instead of silencing them with `noqa: F401`; the
list matches the one `feature/store-i18n` already carries.
- Use `import unittest.mock as mock` in `test_applications.py`, as the
rest of the suite does.
- Spell `_NON_PRINTABLE` in `utils/yaml_bootstrap.py` as the equivalent
BMP-only positive class; it matches exactly the same 2111 code points
as the old negated class across U+0000..U+10FFFF.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Drop the second name for the same constant. Every consumer now uses
`PROJECT_ROOT` exclusively, matching the canonical
`utils.cache.__init__.PROJECT_ROOT` source.
The rename is a word-boundary substitution applied across every
Python file in the repo, validated by `ast.parse` after each edit.
Three files additionally drop a redundant
`PROJECT_ROOT = PROJECT_ROOT` class attribute (it pointed the class-
level name at the module-level name) and inline the
`self.PROJECT_ROOT` reads to plain `PROJECT_ROOT`.
The pass also closes a latent wave-1 inconsistency: a handful of
files received the `from . import PROJECT_ROOT` shim earlier but
their consumer expressions still spelled `REPO_ROOT / "scripts" / …`,
so the shim was unused. The rename ties shim and consumer together.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the existing `test_vars_usage_in_yaml.py` (task-level `vars:`
block scanner) and the new `test_role_and_group_vars_used.py`
(role/group var top-level scanner) into a dedicated
`tests/lint/ansible/variables/` subpackage. The two checks address
orthogonal scopes — task-local `vars:` blocks vs. role-local
`vars/main.yml` / `defaults/main.yml` and `group_vars/**/*.yml` — so
co-locating them documents the variable-lifecycle coverage in one
spot and leaves room for follow-up scanners (host_vars, set_fact,
register, …) without cluttering the parent directory.
The new lint:
- Extracts top-level mapping keys from every
`roles/<role>/{vars,defaults}/main.yml` and
`group_vars/**/*.{yml,yaml}` via the cached `load_yaml_any`,
filtered to identifiers (`[A-Za-z_][A-Za-z0-9_]*`).
- Single project-tree walk over `.yml` / `.yaml` / `.j2` builds two
identifier sets: tokens inside Jinja `{{ … }}` / `{% … %}` blocks,
and identifiers in Ansible expression-bearing keys (`when:`,
`loop:`, `with_*:`). Tokens immediately followed by `(` are
excluded so function/macro calls do not count as variable uses.
- Routes every read through the project caches: `iter_project_files`
(`lru_cache(maxsize=1)`), `read_text` (`lru_cache(maxsize=8192)`),
and `load_yaml_any` (signature-keyed cache). No `yaml.safe_load*`
bypass.
- Whitelists `ansible_python_interpreter` and `ansible_shell_executable`
(Ansible connection-behavior keys consumed by the runner itself).
- Aggregates every undecidable variable into one
`<file>:<line>: '<name>' is never referenced` line and fails once
with the full list, so the reviewer sees the whole drift surface
in a single run.
Surfaces seven existing dead declarations on first run (across
`sys-stk-front-proxy`, `test-e2e-playwright`, `web-app-discourse`,
`web-app-opentalk`, `group_vars/all/`); pruning them is left for
follow-up commits so this one stays purely the lint introduction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>