lint: enable PLW1514 as a blocking ruff rule

Turns the existing 'all lints disabled' stance into 'exactly one lint
enabled' — PLW1514 (unspecified-encoding) catches bare open() /
read_text() / write_text() calls that default to locale encoding on
Windows (cp1252), silently corrupting non-ASCII content.

Changes:

1. pyproject.toml
   - Migrate [tool.ruff] top-level select → [tool.ruff.lint].select
     (deprecated config location, ruff was warning on every run)
   - Add preview = true (PLW1514 is a preview rule in ruff 0.15.x)
   - select = ['PLW1514'] (exactly one rule, deliberately minimal)
   - per-file-ignores exempt tests/, plugins/, skills/, optional-skills/ —
     those have their own conventions or intentionally exercise edge cases

2. website/scripts/extract-skills.py
   - Fix 3 remaining bare opens (website/ was excluded from the main
     sweep but needed for ruff check . to go green)

3. tests/test_lint_config.py (new, 5 tests)
   - Guards against accidental rule removal.  If someone deletes PLW1514
     from the select list or disables preview mode, these tests fail
     with a loud message explaining why the rule exists.

Paired with a companion commit (held locally for now, pending a token
with workflow scope) that adds a blocking ruff step to .github/workflows/
lint.yml.  Without that companion commit, ruff is configured correctly
but nothing in CI enforces it yet — the advisory PR comment will still
surface new PLW1514 violations though, so authors see them.

Verified: ruff check . → exit 0, 0 violations across the repo.
Test suite: 90 passed, 14 skipped, 0 failed.
This commit is contained in:
Teknium 2026-05-07 19:36:13 -07:00
parent 9c914c01c8
commit e0c03defd5
3 changed files with 137 additions and 4 deletions

View file

@ -69,7 +69,7 @@ def extract_local_skills():
continue
skill_path = os.path.join(root, "SKILL.md")
with open(skill_path) as f:
with open(skill_path, encoding="utf-8") as f:
content = f.read()
if not content.startswith("---"):
@ -128,7 +128,7 @@ def extract_cached_index_skills():
filepath = os.path.join(INDEX_CACHE_DIR, filename)
try:
with open(filepath) as f:
with open(filepath, encoding="utf-8") as f:
data = json.load(f)
except (json.JSONDecodeError, OSError):
continue
@ -254,7 +254,7 @@ def main():
))
os.makedirs(os.path.dirname(OUTPUT), exist_ok=True)
with open(OUTPUT, "w") as f:
with open(OUTPUT, "w", encoding="utf-8") as f:
json.dump(all_skills, f, indent=2)
print(f"Extracted {len(all_skills)} skills to {OUTPUT}")