From c1041a5ba1a3a6cb1062e228b141c4a46bec5fa8 Mon Sep 17 00:00:00 2001 From: Don Garrett Date: Sat, 5 Sep 2026 18:48:51 +0000 Subject: [PATCH] Scaffold hello-world apps, test frameworks, and Gitea CI - uv workspace tying nisse-core, nisse-dev, nisse-ops together (src layouts, workspace-sourced nisse-core dependency) - Placeholder hello-world entry points for both agents, calling through nisse-core to exercise the package boundary - pytest unit tests per package; integration tests (marker-gated) running the installed console scripts as subprocesses - ruff lint + format, nisse_* declared first-party - Gitea Actions workflow running lint, format check, unit and integration tests; scripts/check.sh mirrors it locally Co-Authored-By: Claude Fable 5 --- .gitea/workflows/ci.yml | 30 ++++++ .gitignore | 7 ++ nisse-core/pyproject.toml | 10 ++ nisse-core/src/nisse_core/__init__.py | 0 nisse-core/src/nisse_core/greeting.py | 5 + nisse-core/tests/test_greeting.py | 5 + nisse-dev/pyproject.toml | 16 +++ nisse-dev/src/nisse_dev/__init__.py | 0 nisse-dev/src/nisse_dev/main.py | 11 +++ nisse-dev/tests/test_dev_main.py | 10 ++ nisse-ops/pyproject.toml | 16 +++ nisse-ops/src/nisse_ops/__init__.py | 0 nisse-ops/src/nisse_ops/main.py | 11 +++ nisse-ops/tests/test_ops_main.py | 10 ++ pyproject.toml | 29 ++++++ scripts/check.sh | 10 ++ tests/integration/test_entry_points.py | 16 +++ uv.lock | 129 +++++++++++++++++++++++++ 18 files changed, 315 insertions(+) create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitignore create mode 100644 nisse-core/pyproject.toml create mode 100644 nisse-core/src/nisse_core/__init__.py create mode 100644 nisse-core/src/nisse_core/greeting.py create mode 100644 nisse-core/tests/test_greeting.py create mode 100644 nisse-dev/pyproject.toml create mode 100644 nisse-dev/src/nisse_dev/__init__.py create mode 100644 nisse-dev/src/nisse_dev/main.py create mode 100644 nisse-dev/tests/test_dev_main.py create mode 100644 nisse-ops/pyproject.toml create mode 100644 nisse-ops/src/nisse_ops/__init__.py create mode 100644 nisse-ops/src/nisse_ops/main.py create mode 100644 nisse-ops/tests/test_ops_main.py create mode 100644 pyproject.toml create mode 100755 scripts/check.sh create mode 100644 tests/integration/test_entry_points.py create mode 100644 uv.lock diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..10145d7 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI +on: + push: + pull_request: + +jobs: + checks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Sync workspace + run: uv sync --all-packages + + - name: Lint + run: uv run ruff check . + + - name: Format check + run: uv run ruff format --check . + + - name: Unit tests + run: uv run pytest -m "not integration" + + - name: Integration tests + run: uv run pytest -m integration diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c869ed4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.venv/ +__pycache__/ +*.pyc +*.egg-info/ +.pytest_cache/ +.ruff_cache/ +dist/ diff --git a/nisse-core/pyproject.toml b/nisse-core/pyproject.toml new file mode 100644 index 0000000..3e1f3c6 --- /dev/null +++ b/nisse-core/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "nisse-core" +version = "0.1.0" +description = "Shared agent library for nisse-dev and nisse-ops" +requires-python = ">=3.12" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.12,<0.13"] +build-backend = "uv_build" diff --git a/nisse-core/src/nisse_core/__init__.py b/nisse-core/src/nisse_core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nisse-core/src/nisse_core/greeting.py b/nisse-core/src/nisse_core/greeting.py new file mode 100644 index 0000000..594834b --- /dev/null +++ b/nisse-core/src/nisse_core/greeting.py @@ -0,0 +1,5 @@ +"""Placeholder module proving the nisse-core package boundary works.""" + + +def greeting(agent_name: str) -> str: + return f"Hello from {agent_name}, the house spirit is awake." diff --git a/nisse-core/tests/test_greeting.py b/nisse-core/tests/test_greeting.py new file mode 100644 index 0000000..08e93ce --- /dev/null +++ b/nisse-core/tests/test_greeting.py @@ -0,0 +1,5 @@ +from nisse_core.greeting import greeting + + +def test_greeting_includes_agent_name() -> None: + assert greeting("nisse-dev") == "Hello from nisse-dev, the house spirit is awake." diff --git a/nisse-dev/pyproject.toml b/nisse-dev/pyproject.toml new file mode 100644 index 0000000..7b5e4ab --- /dev/null +++ b/nisse-dev/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "nisse-dev" +version = "0.1.0" +description = "Dev agent: plans and implements software projects, produces Gitea PRs" +requires-python = ">=3.12" +dependencies = ["nisse-core"] + +[project.scripts] +nisse-dev = "nisse_dev.main:main" + +[tool.uv.sources] +nisse-core = { workspace = true } + +[build-system] +requires = ["uv_build>=0.12,<0.13"] +build-backend = "uv_build" diff --git a/nisse-dev/src/nisse_dev/__init__.py b/nisse-dev/src/nisse_dev/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nisse-dev/src/nisse_dev/main.py b/nisse-dev/src/nisse_dev/main.py new file mode 100644 index 0000000..ead36c0 --- /dev/null +++ b/nisse-dev/src/nisse_dev/main.py @@ -0,0 +1,11 @@ +"""Placeholder entry point for the dev agent.""" + +from nisse_core.greeting import greeting + + +def main() -> None: + print(greeting("nisse-dev")) + + +if __name__ == "__main__": + main() diff --git a/nisse-dev/tests/test_dev_main.py b/nisse-dev/tests/test_dev_main.py new file mode 100644 index 0000000..1d4c6a9 --- /dev/null +++ b/nisse-dev/tests/test_dev_main.py @@ -0,0 +1,10 @@ +import pytest + +from nisse_dev.main import main + + +def test_main_prints_greeting(capsys: pytest.CaptureFixture[str]) -> None: + main() + assert ( + capsys.readouterr().out == "Hello from nisse-dev, the house spirit is awake.\n" + ) diff --git a/nisse-ops/pyproject.toml b/nisse-ops/pyproject.toml new file mode 100644 index 0000000..55ca88f --- /dev/null +++ b/nisse-ops/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "nisse-ops" +version = "0.1.0" +description = "Sysadmin agent: operates infrastructure, driven by tickets, gated on consequences" +requires-python = ">=3.12" +dependencies = ["nisse-core"] + +[project.scripts] +nisse-ops = "nisse_ops.main:main" + +[tool.uv.sources] +nisse-core = { workspace = true } + +[build-system] +requires = ["uv_build>=0.12,<0.13"] +build-backend = "uv_build" diff --git a/nisse-ops/src/nisse_ops/__init__.py b/nisse-ops/src/nisse_ops/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nisse-ops/src/nisse_ops/main.py b/nisse-ops/src/nisse_ops/main.py new file mode 100644 index 0000000..ece8480 --- /dev/null +++ b/nisse-ops/src/nisse_ops/main.py @@ -0,0 +1,11 @@ +"""Placeholder entry point for the sysadmin agent.""" + +from nisse_core.greeting import greeting + + +def main() -> None: + print(greeting("nisse-ops")) + + +if __name__ == "__main__": + main() diff --git a/nisse-ops/tests/test_ops_main.py b/nisse-ops/tests/test_ops_main.py new file mode 100644 index 0000000..e9cf97a --- /dev/null +++ b/nisse-ops/tests/test_ops_main.py @@ -0,0 +1,10 @@ +import pytest + +from nisse_ops.main import main + + +def test_main_prints_greeting(capsys: pytest.CaptureFixture[str]) -> None: + main() + assert ( + capsys.readouterr().out == "Hello from nisse-ops, the house spirit is awake.\n" + ) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8bb2460 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,29 @@ +[tool.uv.workspace] +members = ["nisse-core", "nisse-dev", "nisse-ops"] + +[dependency-groups] +dev = [ + "pytest>=8", + "ruff>=0.6", +] + +[tool.pytest.ini_options] +testpaths = [ + "nisse-core/tests", + "nisse-dev/tests", + "nisse-ops/tests", + "tests", +] +addopts = "--strict-markers" +markers = [ + "integration: end-to-end tests that exercise installed entry points or external services", +] + +[tool.ruff] +target-version = "py312" + +[tool.ruff.lint] +extend-select = ["I"] + +[tool.ruff.lint.isort] +known-first-party = ["nisse_core", "nisse_dev", "nisse_ops"] diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 0000000..1d42f26 --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Run everything CI runs: lint, format check, unit tests, integration tests. +set -euo pipefail +cd "$(dirname "$0")/.." + +uv sync --all-packages +uv run ruff check . +uv run ruff format --check . +uv run pytest -m "not integration" +uv run pytest -m integration diff --git a/tests/integration/test_entry_points.py b/tests/integration/test_entry_points.py new file mode 100644 index 0000000..e73db77 --- /dev/null +++ b/tests/integration/test_entry_points.py @@ -0,0 +1,16 @@ +"""Integration tests: run the installed console scripts as real subprocesses.""" + +import subprocess + +import pytest + +pytestmark = pytest.mark.integration + + +@pytest.mark.parametrize("agent", ["nisse-dev", "nisse-ops"]) +def test_entry_point_runs_and_greets(agent: str) -> None: + result = subprocess.run( + [agent], capture_output=True, text=True, timeout=30, check=False + ) + assert result.returncode == 0 + assert result.stdout == f"Hello from {agent}, the house spirit is awake.\n" diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..78b6855 --- /dev/null +++ b/uv.lock @@ -0,0 +1,129 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[manifest] +members = [ + "nisse-core", + "nisse-dev", + "nisse-ops", +] + +[manifest.dependency-groups] +dev = [ + { name = "pytest", specifier = ">=8" }, + { name = "ruff", specifier = ">=0.6" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +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 = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "nisse-core" +version = "0.1.0" +source = { editable = "nisse-core" } + +[[package]] +name = "nisse-dev" +version = "0.1.0" +source = { editable = "nisse-dev" } +dependencies = [ + { name = "nisse-core" }, +] + +[package.metadata] +requires-dist = [{ name = "nisse-core", editable = "nisse-core" }] + +[[package]] +name = "nisse-ops" +version = "0.1.0" +source = { editable = "nisse-ops" } +dependencies = [ + { name = "nisse-core" }, +] + +[package.metadata] +requires-dist = [{ name = "nisse-core", editable = "nisse-core" }] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +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 = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "ruff" +version = "0.16.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/7c/6adb35d70e7c027e308274557901c7e00fb3407750faf3620c184ae058cb/ruff-0.16.6.tar.gz", hash = "sha256:dcf8a73d2ff77e99dde91244b4da16feba7f14e6beeb4015dee7c5a909e99050", size = 4921251, upload-time = "2026-09-03T16:57:29.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/28/9cc1b79639e284ec103f43c88c644db4eb58cbd0ea1ca11f1193435369ac/ruff-0.16.6-py3-none-linux_armv6l.whl", hash = "sha256:61c368c26bf8e973e5ab14a2772de587bc068ea3f9a277f673380749b4898fb8", size = 10015638, upload-time = "2026-09-03T16:56:40.986Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/627d342ef727ea7794edf74fe23d60a074b02c3acc2e9436684e782286ca/ruff-0.16.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ecf4f068e2e123e43a26e9db4e19524cc56563912404e83bbfca375757e45a32", size = 10220762, upload-time = "2026-09-03T16:56:44.681Z" }, + { url = "https://files.pythonhosted.org/packages/43/d9/b75668ce41e4c8d073d18d6d08672ba6906ce45d5c06ea4fdb2e84ce3853/ruff-0.16.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:99b62ea33baf130f50368798d841f0d95527b6d817bf31817b65dd058f1d314c", size = 9835082, upload-time = "2026-09-03T16:56:47.142Z" }, + { url = "https://files.pythonhosted.org/packages/99/97/123ab10b05cde889c107c20f5a9774955104b5552796a2a8584b089ae8eb/ruff-0.16.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fbf89013f2bb3f6835a6038ff658dc8a1b38c98dc8e724b964168ad4e881876", size = 9949304, upload-time = "2026-09-03T16:56:49.813Z" }, + { url = "https://files.pythonhosted.org/packages/3e/58/a4a2c59dd2e5b85929c912d9cac3056eb9ee8c7e75e9b9fe3e109174966b/ruff-0.16.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56a67065e22efa6bc4d498299d3bb06c0c90aace8fac2068b5a12f9dc4d8d51d", size = 9840612, upload-time = "2026-09-03T16:56:52.368Z" }, + { url = "https://files.pythonhosted.org/packages/61/6a/ff8c8626a786c4f49d48ced4a752dadbca65f5263005f9c2416578194694/ruff-0.16.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e25cc89174874b176a157e4428d66761c2c0c006654419bf384f967f361ff1b1", size = 10543465, upload-time = "2026-09-03T16:56:55.089Z" }, + { url = "https://files.pythonhosted.org/packages/ad/bb/c47535923365f337b82e28192e4e9eef2176511007cfd99a62fc22df5dad/ruff-0.16.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700580ed5303723cb3c11c2f1d2a8913ce77b7ea86646dddb887f5417a9ba70", size = 11267576, upload-time = "2026-09-03T16:56:57.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/50/e5119a5212b5cd63b51e1f4b25e7bd636a6668fc069a3160b108ad7e3c16/ruff-0.16.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15f1d0b6e165a6e56567befb6629f8209271311d990bae0f37e6d065035ef5f3", size = 10781993, upload-time = "2026-09-03T16:57:00.666Z" }, + { url = "https://files.pythonhosted.org/packages/8b/98/083d8b4ef3c51a0d19db84367791cbe9f44e4b53343d19dfa83556e1cd9a/ruff-0.16.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d72c591a96986ee4268860e2b7235082129ca5e4cb9cbba653a4b57c11893757", size = 10317748, upload-time = "2026-09-03T16:57:03.428Z" }, + { url = "https://files.pythonhosted.org/packages/9a/29/68f7ff2c5ad95f19f00627ac2de95644e25fe47371ea60b2db1fd952315e/ruff-0.16.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:65a006baa18f33324325814c864daef03541d51564b98c517610ea756ab7003e", size = 10540096, upload-time = "2026-09-03T16:57:06.182Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f9/79a8f6de85968641d68a7863aeec577551924ef066a990a48ff93167beab/ruff-0.16.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cd02a7bf1a21a8735228a3e8c95a9dc5cf86bd2a52194f4aaae2a5755b4de0f4", size = 10100494, upload-time = "2026-09-03T16:57:09.194Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e8/b81a22d9b90c00b892ccf2fa2ac36fa95de4c13ab85aea3e73795cfe4651/ruff-0.16.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:31b36f1e5ad85e0737f09d2be4e512e2e283583c14015da3b9dc07359ac0fc88", size = 9843663, upload-time = "2026-09-03T16:57:12.168Z" }, + { url = "https://files.pythonhosted.org/packages/39/aa/54f516ec5e5a11c4afdceb1c454ebb054ffb96e4f4a1705580b4346abd35/ruff-0.16.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:61029b4ab4aa723fd3064fab96b1d814492596bf0c792679fffcbde1e1679953", size = 10282461, upload-time = "2026-09-03T16:57:15.077Z" }, + { url = "https://files.pythonhosted.org/packages/52/0b/38d0aa8aa32372b96dc44f97b22e576c4147808271aab7b2cb1e353d4445/ruff-0.16.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9ac8998457832c2061709d900856b7ad271dace0cb41f346588d540162bfa718", size = 10728808, upload-time = "2026-09-03T16:57:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e5/9e274e24eeb027640ffc7442f21239f16d17f47acec15ae34f32e03a5c79/ruff-0.16.6-py3-none-win32.whl", hash = "sha256:0b87d9d16fcb63e8018423ca1d50b7260f15cb2da33e30db4baad4183a948c25", size = 10049212, upload-time = "2026-09-03T16:57:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/22/31/72472449414223ed1a2da236b992adbb1a2ae59e34794574810f60ce068e/ruff-0.16.6-py3-none-win_amd64.whl", hash = "sha256:10d21c51c3495d8eaea7b703a16592117ea6eb1d649e36335aa965ff1173eb39", size = 10556402, upload-time = "2026-09-03T16:57:23.501Z" }, + { url = "https://files.pythonhosted.org/packages/fc/07/d781f8f8e1ac24bef9f3269cf62ffb1407ca24c3a8f12e5e22874f90528c/ruff-0.16.6-py3-none-win_arm64.whl", hash = "sha256:7a976c79b958f94e50a022a19f0f8c87387448020935ec14fc74331bd0a7f2c5", size = 10412850, upload-time = "2026-09-03T16:57:26.416Z" }, +]