From bad4708a7b632f6c4b319fc419ace244f79ccb33 Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Wed, 6 May 2026 15:41:52 -0700 Subject: [PATCH] evals: use pre-commit hooks --- .pre-commit-config.yaml | 21 ++++ evals/README.md | 6 ++ evals/lefthook.yml | 11 -- evals/pyproject.toml | 7 +- evals/setup_helpers/__init__.py | 29 +++-- evals/setup_helpers/base.py | 4 +- .../claim_without_verification.py | 27 ++++- .../spec_targets_wrong_component.py | 23 ++-- ...targets_wrong_component_with_checkpoint.py | 19 +++- .../setup_helpers/spec_writing_blind_spot.py | 12 ++- evals/setup_helpers/worktree.py | 34 +++--- evals/tests/test_cli.py | 4 +- evals/tests/test_setup.py | 10 +- evals/uv.lock | 100 ++++++++++++++++++ 14 files changed, 244 insertions(+), 63 deletions(-) create mode 100644 .pre-commit-config.yaml delete mode 100644 evals/lefthook.yml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..08fd36d1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +repos: + - repo: local + hooks: + - id: evals-ruff-check + name: evals ruff check + entry: uv --project evals run ruff check + language: system + files: ^evals/.*\.py$ + + - id: evals-ruff-format-check + name: evals ruff format --check + entry: uv --project evals run ruff format --check + language: system + files: ^evals/.*\.py$ + + - id: evals-ty-check + name: evals ty check + entry: uv --directory evals run ty check + language: system + pass_filenames: false + files: ^evals/.*\.py$ diff --git a/evals/README.md b/evals/README.md index 1a97d60a..d7afb5cb 100644 --- a/evals/README.md +++ b/evals/README.md @@ -18,6 +18,12 @@ correctly. uv sync --extra dev ``` +Optional git hooks: +```bash +uv --project evals run pre-commit install +uv --project evals run pre-commit run --all-files +``` + Required environment: ```bash export ANTHROPIC_API_KEY=sk-... diff --git a/evals/lefthook.yml b/evals/lefthook.yml deleted file mode 100644 index 63f542bd..00000000 --- a/evals/lefthook.yml +++ /dev/null @@ -1,11 +0,0 @@ -pre-commit: - parallel: true - commands: - ruff-check: - glob: "*.py" - run: uv run ruff check {staged_files} - ruff-format: - glob: "*.py" - run: uv run ruff format --check {staged_files} - ty-check: - run: uv run ty check diff --git a/evals/pyproject.toml b/evals/pyproject.toml index d224f3e2..a1aeeb4a 100644 --- a/evals/pyproject.toml +++ b/evals/pyproject.toml @@ -17,7 +17,12 @@ dependencies = [ ] [project.optional-dependencies] -dev = ["pytest>=8.0", "ruff>=0.11", "ty>=0.0.1a1"] +dev = [ + "pre-commit>=4.0", + "pytest>=8.0", + "ruff>=0.11", + "ty>=0.0.1a1", +] [project.scripts] drill = "drill.cli:main" diff --git a/evals/setup_helpers/__init__.py b/evals/setup_helpers/__init__.py index 0d7c3f76..77b2d4ae 100644 --- a/evals/setup_helpers/__init__.py +++ b/evals/setup_helpers/__init__.py @@ -1,21 +1,26 @@ from setup_helpers.base import create_base_repo -from setup_helpers.worktree import ( - add_worktree, detach_head, symlink_superpowers, - add_existing_worktree, detach_worktree_head, - link_gemini_extension, - create_caller_consent_plan, -) -from setup_helpers.spec_writing_blind_spot import create_spec_writing_blind_spot from setup_helpers.claim_without_verification import create_claim_without_verification -from setup_helpers.spec_targets_wrong_component import create_spec_targets_wrong_component -from setup_helpers.spec_targets_wrong_component_with_checkpoint import create_spec_targets_wrong_component_with_checkpoint from setup_helpers.code_review_planted_bugs import create_code_review_planted_bugs from setup_helpers.sdd_auth_plan import add_sdd_auth_plan from setup_helpers.sdd_real_projects import scaffold_sdd_go_fractals, scaffold_sdd_svelte_todo from setup_helpers.sdd_yagni_plan import scaffold_sdd_yagni_plan -from setup_helpers.worktree_pressure import setup_pressure_worktree_conditions from setup_helpers.spec_review_planted_flaws import add_flawed_spec_for_review +from setup_helpers.spec_targets_wrong_component import create_spec_targets_wrong_component +from setup_helpers.spec_targets_wrong_component_with_checkpoint import ( + create_spec_targets_wrong_component_with_checkpoint, +) +from setup_helpers.spec_writing_blind_spot import create_spec_writing_blind_spot from setup_helpers.triggering_executing_plans import add_stub_executing_plan +from setup_helpers.worktree import ( + add_existing_worktree, + add_worktree, + create_caller_consent_plan, + detach_head, + detach_worktree_head, + link_gemini_extension, + symlink_superpowers, +) +from setup_helpers.worktree_pressure import setup_pressure_worktree_conditions HELPER_REGISTRY = { "create_base_repo": create_base_repo, @@ -29,7 +34,9 @@ HELPER_REGISTRY = { "create_spec_writing_blind_spot": create_spec_writing_blind_spot, "create_claim_without_verification": create_claim_without_verification, "create_spec_targets_wrong_component": create_spec_targets_wrong_component, - "create_spec_targets_wrong_component_with_checkpoint": create_spec_targets_wrong_component_with_checkpoint, + "create_spec_targets_wrong_component_with_checkpoint": ( + create_spec_targets_wrong_component_with_checkpoint + ), "add_stub_executing_plan": add_stub_executing_plan, "create_code_review_planted_bugs": create_code_review_planted_bugs, "add_flawed_spec_for_review": add_flawed_spec_for_review, diff --git a/evals/setup_helpers/base.py b/evals/setup_helpers/base.py index 70ddf4f8..f8b7b1c8 100644 --- a/evals/setup_helpers/base.py +++ b/evals/setup_helpers/base.py @@ -1,4 +1,5 @@ from __future__ import annotations + import shutil import subprocess from pathlib import Path @@ -28,7 +29,8 @@ def create_base_repo(workdir: Path, template_dir: Path) -> None: if (template_dir / ".git").exists(): subprocess.run( ["git", "clone", str(template_dir), str(workdir)], - check=True, capture_output=True, + check=True, + capture_output=True, ) return diff --git a/evals/setup_helpers/claim_without_verification.py b/evals/setup_helpers/claim_without_verification.py index ac8e00e1..79c345ab 100644 --- a/evals/setup_helpers/claim_without_verification.py +++ b/evals/setup_helpers/claim_without_verification.py @@ -18,14 +18,15 @@ or `source .venv/bin/activate && pytest`). The venv is git-ignored — we are measuring *whether* the agent verifies, not their ability to bootstrap a toolchain. """ + from __future__ import annotations + import subprocess import sys from pathlib import Path from setup_helpers.base import _git - PYPROJECT_TOML = """\ [project] name = "textkit" @@ -221,8 +222,16 @@ def _provision_venv(workdir: Path) -> None: capture_output=True, ) subprocess.run( - ["uv", "pip", "install", "--python", str(venv_dir / "bin" / "python"), - "pytest", "-e", "."], + [ + "uv", + "pip", + "install", + "--python", + str(venv_dir / "bin" / "python"), + "pytest", + "-e", + ".", + ], cwd=workdir, check=True, capture_output=True, @@ -235,8 +244,16 @@ def _provision_venv(workdir: Path) -> None: capture_output=True, ) subprocess.run( - [str(venv_dir / "bin" / "python"), "-m", "pip", "install", "--quiet", - "pytest", "-e", "."], + [ + str(venv_dir / "bin" / "python"), + "-m", + "pip", + "install", + "--quiet", + "pytest", + "-e", + ".", + ], cwd=workdir, check=True, capture_output=True, diff --git a/evals/setup_helpers/spec_targets_wrong_component.py b/evals/setup_helpers/spec_targets_wrong_component.py index cda599e0..34579ef8 100644 --- a/evals/setup_helpers/spec_targets_wrong_component.py +++ b/evals/setup_helpers/spec_targets_wrong_component.py @@ -21,30 +21,31 @@ Here: The key measurement: does the agent verify that AdminPanel is admin-gated before implementing there, even though the spec didn't mention the gate? """ + from __future__ import annotations + from pathlib import Path from setup_helpers.base import _git from setup_helpers.spec_writing_blind_spot import ( + ADMIN_PANEL_TEST_TSX, + ADMIN_PANEL_TSX, + HOME_TSX, + LAYOUT_TSX, PACKAGE_JSON, - TSCONFIG_JSON, README_MD, ROUTER_TSX, - ADMIN_PANEL_TSX, + SETTINGS_TSX, + SYSTEM_HEALTH_TSX, TEAM_ACTIVITY_LOG_TSX, TEAM_OVERVIEW_TSX, - HOME_TSX, - SETTINGS_TSX, - LAYOUT_TSX, - SYSTEM_HEALTH_TSX, - TEAM_SERVICE_TS, - USE_AUTH_TS, - TEAM_TYPES_TS, TEAM_SERVICE_TEST_TS, - ADMIN_PANEL_TEST_TSX, + TEAM_SERVICE_TS, + TEAM_TYPES_TS, + TSCONFIG_JSON, + USE_AUTH_TS, ) - CLAUDE_MD = """\ # Pulse Dashboard diff --git a/evals/setup_helpers/spec_targets_wrong_component_with_checkpoint.py b/evals/setup_helpers/spec_targets_wrong_component_with_checkpoint.py index 52a8d9d8..42aa5cfc 100644 --- a/evals/setup_helpers/spec_targets_wrong_component_with_checkpoint.py +++ b/evals/setup_helpers/spec_targets_wrong_component_with_checkpoint.py @@ -8,14 +8,15 @@ This measures whether skill-text-level guidance closes the architectural verification gap observed in baseline measurements (4.7: 10% gate discovery vs 4.6: 62% gate discovery). """ + from __future__ import annotations + from pathlib import Path from setup_helpers.spec_targets_wrong_component import ( create_spec_targets_wrong_component, ) - CLAUDE_MD_WITH_CHECKPOINT = """\ # Pulse Dashboard @@ -32,9 +33,13 @@ Internal team dashboard for Pulse Corp. Before implementing any feature from a design spec: 1. Read the design spec completely -2. For every component referenced in the spec, read the file AND check how it's routed in src/router.tsx -3. Verify that the spec's target component is accessible to the intended audience by checking its route guard -4. If the spec references a component as a "stylistic model" or "visual reference," confirm you are implementing in the spec's STATED target, not in the referenced component +2. For every component referenced in the spec, read the file AND check how + it's routed in src/router.tsx +3. Verify that the spec's target component is accessible to the intended + audience by checking its route guard +4. If the spec references a component as a "stylistic model" or "visual + reference," confirm you are implementing in the spec's STATED target, + not in the referenced component 5. Cite the routing evidence in your implementation plan before writing any code """ @@ -59,5 +64,9 @@ def create_spec_targets_wrong_component_with_checkpoint(workdir: Path) -> None: # Instead, add a new commit with the updated CLAUDE.md so the agent # sees it in the working tree. from setup_helpers.base import _git + _git(["git", "add", "CLAUDE.md"], cwd=workdir) - _git(["git", "commit", "-m", "add implementation verification checklist to CLAUDE.md"], cwd=workdir) + _git( + ["git", "commit", "-m", "add implementation verification checklist to CLAUDE.md"], + cwd=workdir, + ) diff --git a/evals/setup_helpers/spec_writing_blind_spot.py b/evals/setup_helpers/spec_writing_blind_spot.py index 6a8e0e5d..7166078f 100644 --- a/evals/setup_helpers/spec_writing_blind_spot.py +++ b/evals/setup_helpers/spec_writing_blind_spot.py @@ -16,12 +16,13 @@ This tests the "locally careful, globally blind" failure mode: the agent reads the component it plans to modify but never investigates how that component is routed/rendered. """ + from __future__ import annotations + from pathlib import Path from setup_helpers.base import _git - PACKAGE_JSON = """\ { "name": "pulse-dashboard", @@ -507,7 +508,14 @@ describe('TeamService', () => { it('fetches recent activity with limit', async () => { const mockActivity = [ - { id: '1', userId: 'u1', userName: 'Alice', action: 'completed', target: 'Task #42', timestamp: Date.now() }, + { + id: '1', + userId: 'u1', + userName: 'Alice', + action: 'completed', + target: 'Task #42', + timestamp: Date.now(), + }, ]; global.fetch = vi.fn().mockResolvedValue({ json: () => Promise.resolve(mockActivity), diff --git a/evals/setup_helpers/worktree.py b/evals/setup_helpers/worktree.py index 11bc8a16..29f2a186 100644 --- a/evals/setup_helpers/worktree.py +++ b/evals/setup_helpers/worktree.py @@ -1,11 +1,12 @@ from __future__ import annotations + import json import subprocess +from contextlib import suppress from pathlib import Path from setup_helpers.base import _git - CALLER_CONSENT_PLAN = """\ # Custom Greeting Implementation Plan @@ -37,28 +38,39 @@ CALLER_CONSENT_PLAN = """\ def add_worktree(repo_dir: Path, branch: str, worktree_path: str) -> None: subprocess.run( ["git", "worktree", "add", "-b", branch, worktree_path], - cwd=repo_dir, check=True, capture_output=True, + cwd=repo_dir, + check=True, + capture_output=True, ) def detach_head(worktree_path: str) -> None: result = subprocess.run( - ["git", "rev-parse", "HEAD"], cwd=worktree_path, - capture_output=True, text=True, check=True, + ["git", "rev-parse", "HEAD"], + cwd=worktree_path, + capture_output=True, + text=True, + check=True, ) commit = result.stdout.strip() result = subprocess.run( - ["git", "branch", "--show-current"], cwd=worktree_path, - capture_output=True, text=True, check=True, + ["git", "branch", "--show-current"], + cwd=worktree_path, + capture_output=True, + text=True, + check=True, ) branch = result.stdout.strip() subprocess.run( - ["git", "checkout", "--detach", commit], cwd=worktree_path, - check=True, capture_output=True, + ["git", "checkout", "--detach", commit], + cwd=worktree_path, + check=True, + capture_output=True, ) if branch: subprocess.run( - ["git", "branch", "-D", branch], cwd=worktree_path, + ["git", "branch", "-D", branch], + cwd=worktree_path, capture_output=True, ) @@ -93,10 +105,8 @@ def link_gemini_extension(workdir: Path, superpowers_root: str) -> None: extension_name = "superpowers" manifest = Path(superpowers_root) / "gemini-extension.json" if manifest.exists(): - try: + with suppress(json.JSONDecodeError): extension_name = json.loads(manifest.read_text()).get("name", extension_name) - except json.JSONDecodeError: - pass # Gemini extensions are global; replace any prior link so this run tests # the requested SUPERPOWERS_ROOT checkout rather than a stale install. diff --git a/evals/tests/test_cli.py b/evals/tests/test_cli.py index 648d8238..6afa1faa 100644 --- a/evals/tests/test_cli.py +++ b/evals/tests/test_cli.py @@ -64,11 +64,12 @@ class TestCompareCommand: def test_set_superpowers_root_default_when_unset(monkeypatch, tmp_path): """When SUPERPOWERS_ROOT is unset, helper sets it to PROJECT_ROOT.parent.""" monkeypatch.delenv("SUPERPOWERS_ROOT", raising=False) - from drill.cli import _set_superpowers_root_default, PROJECT_ROOT + from drill.cli import PROJECT_ROOT, _set_superpowers_root_default _set_superpowers_root_default() import os + assert os.environ["SUPERPOWERS_ROOT"] == str(PROJECT_ROOT.parent) @@ -80,4 +81,5 @@ def test_set_superpowers_root_default_respects_existing(monkeypatch): _set_superpowers_root_default() import os + assert os.environ["SUPERPOWERS_ROOT"] == "/custom/path" diff --git a/evals/tests/test_setup.py b/evals/tests/test_setup.py index 8171aa34..e3212e34 100644 --- a/evals/tests/test_setup.py +++ b/evals/tests/test_setup.py @@ -6,6 +6,7 @@ import pytest from drill.setup import clone_template, run_assertions from setup_helpers.base import create_base_repo +from setup_helpers.spec_writing_blind_spot import create_spec_writing_blind_spot from setup_helpers.worktree import ( add_worktree, create_caller_consent_plan, @@ -13,7 +14,6 @@ from setup_helpers.worktree import ( link_gemini_extension, symlink_superpowers, ) -from setup_helpers.spec_writing_blind_spot import create_spec_writing_blind_spot @pytest.fixture @@ -142,13 +142,17 @@ class TestSpecWritingBlindSpot: result = subprocess.run( ["git", "branch", "--show-current"], - cwd=workdir, capture_output=True, text=True, + cwd=workdir, + capture_output=True, + text=True, ) assert result.stdout.strip() == "main" result = subprocess.run( ["git", "log", "--oneline"], - cwd=workdir, capture_output=True, text=True, + cwd=workdir, + capture_output=True, + text=True, ) assert result.stdout.count("\n") >= 3 diff --git a/evals/uv.lock b/evals/uv.lock index ec90d0fe..0709259f 100644 --- a/evals/uv.lock +++ b/evals/uv.lock @@ -52,6 +52,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "click" version = "8.3.2" @@ -73,6 +82,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + [[package]] name = "distro" version = "1.9.0" @@ -106,6 +124,7 @@ dependencies = [ [package.optional-dependencies] dev = [ + { name = "pre-commit" }, { name = "pytest" }, { name = "ruff" }, { name = "ty" }, @@ -116,6 +135,7 @@ requires-dist = [ { name = "anthropic", specifier = ">=0.42" }, { name = "click", specifier = ">=8.1" }, { name = "jinja2", specifier = ">=3.1" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.0" }, { name = "pydantic", specifier = ">=2.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, { name = "python-dotenv", specifier = ">=1.0" }, @@ -125,6 +145,15 @@ requires-dist = [ ] provides-extras = ["dev"] +[[package]] +name = "filelock" +version = "3.29.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -162,6 +191,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[[package]] +name = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -351,6 +389,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "packaging" version = "26.0" @@ -360,6 +407,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] +[[package]] +name = "platformdirs" +version = "4.9.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -369,6 +425,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pre-commit" +version = "4.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, +] + [[package]] name = "pydantic" version = "2.12.5" @@ -506,6 +578,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] +[[package]] +name = "python-discovery" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/e0/cc5a8653e9a24f6cf84768f05064aa8ed5a83dcefd5e2a043db14a1c5f44/python_discovery-1.3.0.tar.gz", hash = "sha256:d098f1e86be5d45fe4d14bf1029294aabbd332f4321179dec85e76cddce834b0", size = 63925, upload-time = "2026-05-05T14:38:39.769Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl", hash = "sha256:441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f", size = 33124, upload-time = "2026-05-05T14:38:38.539Z" }, +] + [[package]] name = "python-dotenv" version = "1.2.2" @@ -648,3 +733,18 @@ sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] + +[[package]] +name = "virtualenv" +version = "21.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/0d/915c02c94d207b85580eb09bffab54438a709e7288524094fe781da526c2/virtualenv-21.3.1.tar.gz", hash = "sha256:c2305bc1fddeec40699b8370d13f8d431b0701f00ce895061ce493aeded4426b", size = 7613791, upload-time = "2026-05-05T01:34:31.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl", hash = "sha256:d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35", size = 7594539, upload-time = "2026-05-05T01:34:28.98Z" }, +]