This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git


The following commit(s) were added to refs/heads/main by this push:
     new 13c9f6d  feat(spec-status-index): add deterministic spec-status index 
tool (#254)
13c9f6d is described below

commit 13c9f6db84e90dbd4642857611e2755cacc3e448
Author: Justin Mclean <[email protected]>
AuthorDate: Wed May 27 07:14:26 2026 +1000

    feat(spec-status-index): add deterministic spec-status index tool (#254)
    
    New uv tool tools/spec-status-index/ reads YAML frontmatter from
    tools/spec-loop/specs/*.md and prints specs grouped by status.
    Supports --ready (proposed + experimental), --status <value>, and
    --json for machine-readable output so build iterations can select
    the next work item mechanically.
    
    Ships 24 unit tests covering frontmatter parsing, load_specs
    filtering/sorting, format_table, format_json, status ordering,
    and integration smoke-tests against the live specs directory.
    
    Generated-by: Claude (Opus 4.7)
---
 tools/spec-status-index/README.md                  |  54 ++++
 tools/spec-status-index/pyproject.toml             |  87 ++++++
 .../src/spec_status_index/__init__.py              | 263 ++++++++++++++++++
 tools/spec-status-index/tests/__init__.py          |   0
 .../tests/test_spec_status_index.py                | 306 ++++++++++++++++++++
 tools/spec-status-index/uv.lock                    | 307 +++++++++++++++++++++
 6 files changed, 1017 insertions(+)

diff --git a/tools/spec-status-index/README.md 
b/tools/spec-status-index/README.md
new file mode 100644
index 0000000..844a709
--- /dev/null
+++ b/tools/spec-status-index/README.md
@@ -0,0 +1,54 @@
+<!-- START doctoc generated TOC please keep comment here to allow auto update 
-->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents**  *generated with 
[DocToc](https://github.com/thlorenz/doctoc)*
+
+- [spec-status-index](#spec-status-index)
+  - [Usage](#usage)
+  - [Statuses](#statuses)
+  - [Run tests](#run-tests)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+<!-- SPDX-License-Identifier: Apache-2.0
+     https://www.apache.org/licenses/LICENSE-2.0 -->
+
+# spec-status-index
+
+A deterministic `uv` tool that reads spec-loop specs from
+`tools/spec-loop/specs/` and prints them grouped by status, so build
+iterations can choose the next work item mechanically.
+
+## Usage
+
+```bash
+# All specs, grouped by status
+uv run --project tools/spec-status-index spec-status
+
+# Only actionable items (proposed + experimental)
+uv run --project tools/spec-status-index spec-status --ready
+
+# Filter to a specific status
+uv run --project tools/spec-status-index spec-status --status proposed
+
+# Machine-readable JSON output
+uv run --project tools/spec-status-index spec-status --json
+uv run --project tools/spec-status-index spec-status --ready --json
+```
+
+## Statuses
+
+| Status | Meaning |
+|---|---|
+| `proposed` | Planned; no implementation yet |
+| `experimental` | Partially implemented; may change |
+| `stable` | Production quality |
+| `off` | Disabled |
+
+`--ready` surfaces `proposed` and `experimental` specs — the ones with
+actionable build work remaining.
+
+## Run tests
+
+```bash
+uv run --project tools/spec-status-index --group dev pytest
+```
diff --git a/tools/spec-status-index/pyproject.toml 
b/tools/spec-status-index/pyproject.toml
new file mode 100644
index 0000000..5820960
--- /dev/null
+++ b/tools/spec-status-index/pyproject.toml
@@ -0,0 +1,87 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "spec-status-index"
+version = "0.1.0"
+description = "Print spec-loop specs grouped by status; --ready filters to 
actionable items."
+readme = "README.md"
+requires-python = ">=3.11"
+license = { text = "Apache-2.0" }
+dependencies = []
+
+[project.scripts]
+spec-status = "spec_status_index:main"
+
+[dependency-groups]
+dev = [
+  "mypy>=1.11",
+  "pytest>=8.0",
+  "ruff>=0.6",
+]
+
+[tool.hatch.build.targets.wheel]
+packages = ["src/spec_status_index"]
+
+[tool.ruff]
+line-length = 110
+target-version = "py311"
+src = ["src", "tests"]
+
+[tool.ruff.lint]
+select = [
+  "E",
+  "W",
+  "F",
+  "I",
+  "B",
+  "UP",
+  "SIM",
+  "C4",
+  "RUF",
+]
+ignore = [
+  "E501",
+]
+
+[tool.ruff.lint.per-file-ignores]
+"tests/**" = ["B", "SIM"]
+
+[tool.mypy]
+python_version = "3.11"
+files = ["src", "tests"]
+warn_unused_ignores = true
+warn_redundant_casts = true
+warn_unreachable = true
+check_untyped_defs = true
+no_implicit_optional = true
+disallow_untyped_defs = true
+disallow_incomplete_defs = true
+
+[[tool.mypy.overrides]]
+module = "tests.*"
+disallow_untyped_defs = false
+disallow_incomplete_defs = false
+
+[tool.pytest.ini_options]
+minversion = "8.0"
+addopts = "-ra -q"
+testpaths = ["tests"]
diff --git a/tools/spec-status-index/src/spec_status_index/__init__.py 
b/tools/spec-status-index/src/spec_status_index/__init__.py
new file mode 100644
index 0000000..5466dad
--- /dev/null
+++ b/tools/spec-status-index/src/spec_status_index/__init__.py
@@ -0,0 +1,263 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Print spec-loop specs grouped by status.
+
+Reads YAML frontmatter from tools/spec-loop/specs/*.md and prints a
+table grouped by status.  ``--ready`` filters to actionable specs
+(status: proposed or experimental) so build iterations can choose
+the next work item mechanically.
+
+Run from repo root:
+    uv run --project tools/spec-status-index spec-status
+    uv run --project tools/spec-status-index spec-status --ready
+    uv run --project tools/spec-status-index spec-status --status proposed
+    uv run --project tools/spec-status-index spec-status --json
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import re
+import sys
+from dataclasses import dataclass
+from pathlib import Path
+
+# ---------------------------------------------------------------------------
+# Constants
+# ---------------------------------------------------------------------------
+
+SPECS_DIR = Path("tools/spec-loop/specs")
+
+KNOWN_STATUSES = ("stable", "experimental", "proposed", "off")
+
+# Specs that are ready to be picked up for build iterations.
+READY_STATUSES = frozenset({"proposed", "experimental"})
+
+# Files without standard spec frontmatter that are skipped.
+SKIP_FILES = frozenset({"README.md", "overview.md"})
+
+# Display order for statuses.
+STATUS_ORDER = {"proposed": 0, "experimental": 1, "stable": 2, "off": 3}
+
+
+# ---------------------------------------------------------------------------
+# Data model
+# ---------------------------------------------------------------------------
+
+
+@dataclass
+class SpecEntry:
+    file: Path
+    title: str
+    status: str
+    kind: str
+    mode: str
+
+
+# ---------------------------------------------------------------------------
+# Frontmatter parsing
+# ---------------------------------------------------------------------------
+
+_FM_RE = re.compile(r"^---\n(.*?)\n---", re.DOTALL)
+_FIELD_RE = re.compile(r"^(\w+)\s*:\s*(.+)$", re.MULTILINE)
+# Specs may open with leading HTML comments (e.g. the SPDX licence header)
+# before the `---` frontmatter block; strip them so the delimiter can start.
+_LEADING_COMMENTS_RE = re.compile(r"^\s*(?:<!--.*?-->\s*)+", re.DOTALL)
+
+
+def parse_frontmatter(text: str) -> dict[str, str]:
+    """Return a flat dict of scalar frontmatter fields.
+
+    Block scalars (``>``, ``|``) and multi-line values are omitted —
+    only single-line key: value pairs are captured, which is all that
+    spec frontmatter uses for the index fields (title, status, kind, mode).
+    """
+    m = _FM_RE.match(_LEADING_COMMENTS_RE.sub("", text, count=1))
+    if not m:
+        return {}
+    block = m.group(1)
+    result: dict[str, str] = {}
+    for key, val in _FIELD_RE.findall(block):
+        result[key] = val.strip()
+    return result
+
+
+def find_repo_root(start: Path) -> Path:
+    p = start.resolve()
+    while p != p.parent:
+        if (p / ".git").exists():
+            return p
+        p = p.parent
+    raise RuntimeError(f"Could not find repo root (.git) from {start}")
+
+
+# ---------------------------------------------------------------------------
+# Loading
+# ---------------------------------------------------------------------------
+
+
+def load_specs(specs_dir: Path) -> list[SpecEntry]:
+    """Return SpecEntry list sorted by status priority then title."""
+    entries: list[SpecEntry] = []
+    for md_file in sorted(specs_dir.glob("*.md")):
+        if md_file.name in SKIP_FILES:
+            continue
+        text = md_file.read_text()
+        fm = parse_frontmatter(text)
+        if not fm.get("status"):
+            continue
+        entries.append(
+            SpecEntry(
+                file=md_file,
+                title=fm.get("title", md_file.stem),
+                status=fm.get("status", "unknown"),
+                kind=fm.get("kind", ""),
+                mode=fm.get("mode", ""),
+            )
+        )
+    entries.sort(key=lambda e: (STATUS_ORDER.get(e.status, 99), 
e.title.lower()))
+    return entries
+
+
+# ---------------------------------------------------------------------------
+# Output formatters
+# ---------------------------------------------------------------------------
+
+
+def _col_widths(entries: list[SpecEntry]) -> tuple[int, int, int, int]:
+    title_w = max((len(e.title) for e in entries), default=5)
+    status_w = max((len(e.status) for e in entries), default=6)
+    kind_w = max((len(e.kind) for e in entries), default=4)
+    mode_w = max((len(e.mode) for e in entries), default=4)
+    return (
+        max(title_w, 5),
+        max(status_w, 6),
+        max(kind_w, 4),
+        max(mode_w, 4),
+    )
+
+
+def format_table(entries: list[SpecEntry]) -> str:
+    if not entries:
+        return "(no specs matched)\n"
+
+    tw, sw, kw, mw = _col_widths(entries)
+    header = f"{'Title':<{tw}}  {'Status':<{sw}}  {'Kind':<{kw}}  
{'Mode':<{mw}}  File"
+    sep = "-" * len(header)
+
+    lines = [header, sep]
+    current_status = ""
+    for e in entries:
+        if e.status != current_status:
+            if current_status:
+                lines.append("")
+            current_status = e.status
+        lines.append(f"{e.title:<{tw}}  {e.status:<{sw}}  {e.kind:<{kw}}  
{e.mode:<{mw}}  {e.file.name}")
+    lines.append("")
+    return "\n".join(lines)
+
+
+def format_json(entries: list[SpecEntry]) -> str:
+    return json.dumps(
+        [
+            {
+                "title": e.title,
+                "status": e.status,
+                "kind": e.kind,
+                "mode": e.mode,
+                "file": e.file.name,
+            }
+            for e in entries
+        ],
+        indent=2,
+    )
+
+
+# ---------------------------------------------------------------------------
+# CLI
+# ---------------------------------------------------------------------------
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(
+        description=(
+            "Print spec-loop specs by status. "
+            "Use --ready to filter to actionable items (proposed + 
experimental)."
+        )
+    )
+    parser.add_argument(
+        "--ready",
+        action="store_true",
+        help="Show only specs with status 'proposed' or 'experimental'.",
+    )
+    parser.add_argument(
+        "--status",
+        metavar="STATUS",
+        help=f"Filter to a specific status. One of: {', 
'.join(KNOWN_STATUSES)}.",
+    )
+    parser.add_argument(
+        "--json",
+        dest="as_json",
+        action="store_true",
+        help="Emit JSON instead of a text table.",
+    )
+    parser.add_argument(
+        "--specs-dir",
+        type=Path,
+        default=None,
+        help="Override path to the specs directory (default: 
tools/spec-loop/specs relative to repo root).",
+    )
+    args = parser.parse_args()
+
+    try:
+        repo_root = find_repo_root(Path.cwd())
+    except RuntimeError as exc:
+        print(f"error: {exc}", file=sys.stderr)
+        sys.exit(1)
+
+    specs_dir = args.specs_dir if args.specs_dir else repo_root / SPECS_DIR
+    if not specs_dir.is_dir():
+        print(f"error: specs directory not found: {specs_dir}", 
file=sys.stderr)
+        sys.exit(1)
+
+    entries = load_specs(specs_dir)
+
+    if args.ready and args.status:
+        print("error: --ready and --status are mutually exclusive", 
file=sys.stderr)
+        sys.exit(1)
+
+    if args.ready:
+        entries = [e for e in entries if e.status in READY_STATUSES]
+    elif args.status:
+        if args.status not in KNOWN_STATUSES:
+            print(
+                f"error: unknown status {args.status!r}. Known: {', 
'.join(KNOWN_STATUSES)}",
+                file=sys.stderr,
+            )
+            sys.exit(1)
+        entries = [e for e in entries if e.status == args.status]
+
+    if args.as_json:
+        print(format_json(entries))
+    else:
+        print(format_table(entries), end="")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tools/spec-status-index/tests/__init__.py 
b/tools/spec-status-index/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tools/spec-status-index/tests/test_spec_status_index.py 
b/tools/spec-status-index/tests/test_spec_status_index.py
new file mode 100644
index 0000000..e473cd1
--- /dev/null
+++ b/tools/spec-status-index/tests/test_spec_status_index.py
@@ -0,0 +1,306 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Tests for spec-status-index."""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+
+import pytest
+
+from spec_status_index import (
+    READY_STATUSES,
+    SKIP_FILES,
+    STATUS_ORDER,
+    SpecEntry,
+    format_json,
+    format_table,
+    load_specs,
+    parse_frontmatter,
+)
+
+# ---------------------------------------------------------------------------
+# parse_frontmatter
+# ---------------------------------------------------------------------------
+
+
+class TestParseFrontmatter:
+    def test_parses_scalar_fields(self) -> None:
+        text = "---\ntitle: My Spec\nstatus: stable\nkind: feature\nmode: 
Triage\n---\n# body\n"
+        fm = parse_frontmatter(text)
+        assert fm["title"] == "My Spec"
+        assert fm["status"] == "stable"
+        assert fm["kind"] == "feature"
+        assert fm["mode"] == "Triage"
+
+    def test_no_frontmatter_returns_empty(self) -> None:
+        text = "# Just a heading\nno frontmatter here\n"
+        assert parse_frontmatter(text) == {}
+
+    def test_block_scalar_ignored(self) -> None:
+        # Multi-line block scalars (source: >) are not captured; single-line 
fields are.
+        text = "---\ntitle: Spec Title\nstatus: proposed\nsource: >\n  Some 
long text\n  on multiple lines\n---\n"
+        fm = parse_frontmatter(text)
+        assert fm["title"] == "Spec Title"
+        assert fm["status"] == "proposed"
+        # 'source' starts a block scalar — not captured as a simple field.
+        assert "source" not in fm or fm.get("source") in (">", None, "")
+
+    def test_strips_whitespace_from_values(self) -> None:
+        text = "---\ntitle:   Padded Title   \nstatus: experimental\n---\n"
+        fm = parse_frontmatter(text)
+        assert fm["title"] == "Padded Title"
+
+    def test_empty_frontmatter_block(self) -> None:
+        text = "---\n---\n# body\n"
+        fm = parse_frontmatter(text)
+        assert fm == {}
+
+    def test_parses_frontmatter_after_leading_spdx_comment(self) -> None:
+        # Real specs open with an SPDX licence comment before the `---` block.
+        text = (
+            "<!-- SPDX-License-Identifier: Apache-2.0\n"
+            "     https://www.apache.org/licenses/LICENSE-2.0 -->\n\n"
+            "---\ntitle: Commented Spec\nstatus: experimental\nkind: feature\n"
+            "mode: Triage\n---\n# body\n"
+        )
+        fm = parse_frontmatter(text)
+        assert fm["status"] == "experimental"
+        assert fm["title"] == "Commented Spec"
+
+
+# ---------------------------------------------------------------------------
+# load_specs (uses a temp directory with synthetic spec files)
+# ---------------------------------------------------------------------------
+
+
+def _write_spec(
+    directory: Path, filename: str, title: str, status: str, kind: str = 
"feature", mode: str = "infra"
+) -> Path:
+    """Write a minimal spec file and return the path."""
+    path = directory / filename
+    path.write_text(f"---\ntitle: {title}\nstatus: {status}\nkind: 
{kind}\nmode: {mode}\n---\n\n# {title}\n")
+    return path
+
+
+class TestLoadSpecs:
+    def test_loads_valid_specs(self, tmp_path: Path) -> None:
+        _write_spec(tmp_path, "alpha.md", "Alpha Feature", "stable")
+        _write_spec(tmp_path, "beta.md", "Beta Feature", "proposed")
+        entries = load_specs(tmp_path)
+        assert len(entries) == 2
+        titles = {e.title for e in entries}
+        assert "Alpha Feature" in titles
+        assert "Beta Feature" in titles
+
+    def test_skip_files_are_excluded(self, tmp_path: Path) -> None:
+        for name in SKIP_FILES:
+            (tmp_path / name).write_text("---\ntitle: Skip Me\nstatus: 
stable\n---\n")
+        _write_spec(tmp_path, "real.md", "Real Spec", "stable")
+        entries = load_specs(tmp_path)
+        assert len(entries) == 1
+        assert entries[0].title == "Real Spec"
+
+    def test_files_without_status_are_excluded(self, tmp_path: Path) -> None:
+        (tmp_path / "no-status.md").write_text("---\ntitle: No Status\n---\n# 
body\n")
+        _write_spec(tmp_path, "good.md", "Good Spec", "experimental")
+        entries = load_specs(tmp_path)
+        assert len(entries) == 1
+        assert entries[0].title == "Good Spec"
+
+    def test_entries_sorted_by_status_then_title(self, tmp_path: Path) -> None:
+        _write_spec(tmp_path, "c.md", "Charlie", "stable")
+        _write_spec(tmp_path, "a.md", "Alpha", "proposed")
+        _write_spec(tmp_path, "b.md", "Bravo", "experimental")
+        entries = load_specs(tmp_path)
+        statuses = [e.status for e in entries]
+        # proposed (0) < experimental (1) < stable (2)
+        assert statuses == ["proposed", "experimental", "stable"]
+
+    def test_same_status_sorted_alphabetically(self, tmp_path: Path) -> None:
+        _write_spec(tmp_path, "z.md", "Zebra Spec", "stable")
+        _write_spec(tmp_path, "a.md", "Apple Spec", "stable")
+        entries = load_specs(tmp_path)
+        assert entries[0].title == "Apple Spec"
+        assert entries[1].title == "Zebra Spec"
+
+    def test_empty_directory(self, tmp_path: Path) -> None:
+        entries = load_specs(tmp_path)
+        assert entries == []
+
+    def test_fields_populated_correctly(self, tmp_path: Path) -> None:
+        _write_spec(tmp_path, "myspec.md", "My Spec", "experimental", 
kind="fix", mode="Triage")
+        entries = load_specs(tmp_path)
+        assert len(entries) == 1
+        e = entries[0]
+        assert e.title == "My Spec"
+        assert e.status == "experimental"
+        assert e.kind == "fix"
+        assert e.mode == "Triage"
+        assert e.file.name == "myspec.md"
+
+
+# ---------------------------------------------------------------------------
+# READY_STATUSES
+# ---------------------------------------------------------------------------
+
+
+class TestReadyStatuses:
+    def test_ready_includes_proposed_and_experimental(self) -> None:
+        assert "proposed" in READY_STATUSES
+        assert "experimental" in READY_STATUSES
+
+    def test_ready_excludes_stable_and_off(self) -> None:
+        assert "stable" not in READY_STATUSES
+        assert "off" not in READY_STATUSES
+
+
+# ---------------------------------------------------------------------------
+# format_table
+# ---------------------------------------------------------------------------
+
+
+class TestFormatTable:
+    def _make_entry(
+        self, title: str, status: str, kind: str = "feature", mode: str = 
"infra", filename: str = "spec.md"
+    ) -> SpecEntry:
+        return SpecEntry(file=Path(filename), title=title, status=status, 
kind=kind, mode=mode)
+
+    def test_empty_list(self) -> None:
+        output = format_table([])
+        assert "no specs matched" in output
+
+    def test_contains_header(self) -> None:
+        entry = self._make_entry("My Spec", "stable")
+        output = format_table([entry])
+        assert "Title" in output
+        assert "Status" in output
+        assert "Kind" in output
+        assert "Mode" in output
+        assert "File" in output
+
+    def test_contains_spec_data(self) -> None:
+        entry = self._make_entry("My Spec", "proposed", mode="Triage")
+        output = format_table([entry])
+        assert "My Spec" in output
+        assert "proposed" in output
+        assert "Triage" in output
+        assert "spec.md" in output
+
+    def test_multiple_statuses_have_blank_separator(self) -> None:
+        entries = [
+            self._make_entry("Spec A", "proposed", filename="a.md"),
+            self._make_entry("Spec B", "stable", filename="b.md"),
+        ]
+        output = format_table(entries)
+        # There should be a blank line between the status groups.
+        assert "\n\n" in output
+
+
+# ---------------------------------------------------------------------------
+# format_json
+# ---------------------------------------------------------------------------
+
+
+class TestFormatJson:
+    def test_valid_json_output(self) -> None:
+        entries = [
+            SpecEntry(file=Path("spec.md"), title="My Spec", status="stable", 
kind="feature", mode="infra")
+        ]
+        output = format_json(entries)
+        parsed = json.loads(output)
+        assert isinstance(parsed, list)
+        assert len(parsed) == 1
+        assert parsed[0]["title"] == "My Spec"
+        assert parsed[0]["status"] == "stable"
+        assert parsed[0]["file"] == "spec.md"
+
+    def test_empty_list(self) -> None:
+        output = format_json([])
+        parsed = json.loads(output)
+        assert parsed == []
+
+    def test_all_fields_present(self) -> None:
+        entries = [SpecEntry(file=Path("x.md"), title="X", status="proposed", 
kind="fix", mode="Triage")]
+        parsed = json.loads(format_json(entries))
+        assert set(parsed[0].keys()) == {"title", "status", "kind", "mode", 
"file"}
+
+
+# ---------------------------------------------------------------------------
+# STATUS_ORDER
+# ---------------------------------------------------------------------------
+
+
+class TestStatusOrder:
+    def test_proposed_before_experimental(self) -> None:
+        assert STATUS_ORDER["proposed"] < STATUS_ORDER["experimental"]
+
+    def test_experimental_before_stable(self) -> None:
+        assert STATUS_ORDER["experimental"] < STATUS_ORDER["stable"]
+
+    def test_stable_before_off(self) -> None:
+        assert STATUS_ORDER["stable"] < STATUS_ORDER["off"]
+
+
+# ---------------------------------------------------------------------------
+# Integration: load real specs if present
+# ---------------------------------------------------------------------------
+
+
+class TestRealSpecs:
+    """Smoke-test against the actual specs directory when present."""
+
+    @pytest.fixture
+    def specs_dir(self) -> Path | None:
+        # Walk up from the test file to find the repo root.
+        p = Path(__file__).resolve().parent
+        while p != p.parent:
+            candidate = p / "tools" / "spec-loop" / "specs"
+            if candidate.is_dir():
+                return candidate
+            p = p.parent
+        return None
+
+    def test_real_specs_loadable(self, specs_dir: Path | None) -> None:
+        if specs_dir is None:
+            pytest.skip("spec-loop/specs directory not found")
+        entries = load_specs(specs_dir)
+        # There should be at least a few specs.
+        assert len(entries) >= 3
+
+    def test_real_specs_have_known_statuses(self, specs_dir: Path | None) -> 
None:
+        from spec_status_index import KNOWN_STATUSES
+
+        if specs_dir is None:
+            pytest.skip("spec-loop/specs directory not found")
+        entries = load_specs(specs_dir)
+        known = set(KNOWN_STATUSES)
+        for e in entries:
+            assert e.status in known, f"{e.file.name} has unknown status 
{e.status!r}"
+
+    def test_ready_filter_subset_of_all(self, specs_dir: Path | None) -> None:
+        if specs_dir is None:
+            pytest.skip("spec-loop/specs directory not found")
+        all_entries = load_specs(specs_dir)
+        ready = [e for e in all_entries if e.status in READY_STATUSES]
+        assert len(ready) <= len(all_entries)
+        # Every ready entry must appear in all_entries.
+        all_titles = {e.title for e in all_entries}
+        for e in ready:
+            assert e.title in all_titles
diff --git a/tools/spec-status-index/uv.lock b/tools/spec-status-index/uv.lock
new file mode 100644
index 0000000..4a1c01c
--- /dev/null
+++ b/tools/spec-status-index/uv.lock
@@ -0,0 +1,307 @@
+version = 1
+revision = 3
+requires-python = ">=3.11"
+resolution-markers = [
+    "python_full_version >= '3.15'",
+    "python_full_version < '3.15'",
+]
+
+[options]
+exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included 
for backwards compatibility when using relative exclude-newer values.
+exclude-newer-span = "P7D"
+
+[[package]]
+name = "ast-serialize"
+version = "0.4.0"
+source = { registry = "https://pypi.org/simple"; }
+sdist = { url = 
"https://files.pythonhosted.org/packages/e2/1f/50f241d4e01fe75f4bba6a209edd4047c4b26acf70992ff885fd161f79cb/ast_serialize-0.4.0.tar.gz";,
 hash = 
"sha256:74e4e634ab82d1466acf0be27043178570b98ebeaa3165f9240a6fad4c286471", size 
= 60687, upload-time = "2026-05-14T22:44:38.251Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/b0/85/232631c59b5ca7152c08f026e9a46f47d852298acff74edd04a1fc1d0005/ast_serialize-0.4.0-cp314-cp314t-macosx_10_12_x86_64.whl";,
 hash = 
"sha256:a6f26937ce0293aafbece0e39019e020369a5a70486ff4088227f0cc888844a9", size 
= 1182685, upload-time = "2026-05-14T22:43:40.205Z" },
+    { url = 
"https://files.pythonhosted.org/packages/5d/5e/4838d4d3ddc4425555601467d4e2a565e4340899e45feee4e32c80fbc911/ast_serialize-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl";,
 hash = 
"sha256:074032142777e3e6091977dc3c5146a8ca58ae6825b7f64e9a0b604153ddabd8", size 
= 1173113, upload-time = "2026-05-14T22:43:41.937Z" },
+    { url = 
"https://files.pythonhosted.org/packages/22/fc/d622b19fc1c79a62028ec17f4ad4323177af25b174d32b07c84d61ef9d47/ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl";,
 hash = 
"sha256:404f3462b4532e13a70b8849bba241dbd82e30043ff58d98c7e762fd925b116a", size 
= 1234117, upload-time = "2026-05-14T22:43:43.977Z" },
+    { url = 
"https://files.pythonhosted.org/packages/d5/b5/72f8c8659da0b64562e6d97f852d5c2022c74577df27c922e1e7065039ce/ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl";,
 hash = 
"sha256:97c55336e16f5c4ca2bde7be94cca4b8f7d665d64f7008925a82e02707ba14ac", size 
= 1231703, upload-time = "2026-05-14T22:43:46.064Z" },
+    { url = 
"https://files.pythonhosted.org/packages/7b/98/ccc51ee4f90f97a1ed0a0848bd4c9d77a80969849db8a262b7d2970a6a15/ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl";,
 hash = 
"sha256:732b4ef76adcb0f298a7d18c4558336d83b1384f9ae0c7eaa1dc8d031b0a4390", size 
= 1441574, upload-time = "2026-05-14T22:43:47.784Z" },
+    { url = 
"https://files.pythonhosted.org/packages/6a/ce/668c4efe79e09c9cc97a4d0a1c29e61fe6f78857fe1e57c086772af55f89/ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl";,
 hash = 
"sha256:b3db87c4772097c0782250bcd550d66b1189a8c889793c7bcf153f4fee70005c", size 
= 1254040, upload-time = "2026-05-14T22:43:49.879Z" },
+    { url = 
"https://files.pythonhosted.org/packages/3d/be/38b27bc2909b7236939801ca9f0d97cdc6198da4f435a81658e0db506fdb/ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl";,
 hash = 
"sha256:43729a5e369ebbe7750635c0c206bc616fcd36e703cb9c4497d6b4df0291ee64", size 
= 1257847, upload-time = "2026-05-14T22:43:51.607Z" },
+    { url = 
"https://files.pythonhosted.org/packages/68/df/360ebccc361235c167a8be2a0476870cb9ef44c42413bf1289b885684052/ast_serialize-0.4.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl";,
 hash = 
"sha256:91d3786f3929786cdc4eeedfd110abb4603e7f6c1390c5af398f333a947b742d", size 
= 1298683, upload-time = "2026-05-14T22:43:53.606Z" },
+    { url = 
"https://files.pythonhosted.org/packages/51/5c/7d5e0b4d47aafa1600c19e3670f962f81a9bf3da1bc25a1382529a447cf3/ast_serialize-0.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl";,
 hash = 
"sha256:7fba7315fd4bd87cb5560792709f6e66e0606402d362c0a38dd32dfb66ba6066", size 
= 1409438, upload-time = "2026-05-14T22:43:55.316Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b5/3d/8875b2f1af3ec1539b88ff193dfbfa5573084ef7fcab27ea4cd09b6dc829/ast_serialize-0.4.0-cp314-cp314t-musllinux_1_2_armv7l.whl";,
 hash = 
"sha256:4db9769d57deb5545ce56ebbbbe3436dcc0ae2688ce14c295cd14e106624ece7", size 
= 1507922, upload-time = "2026-05-14T22:43:56.959Z" },
+    { url = 
"https://files.pythonhosted.org/packages/55/c3/5ec6927eb493ece7ba64263cdc556be889e0c62a013b1851bbe674a0dcda/ast_serialize-0.4.0-cp314-cp314t-musllinux_1_2_i686.whl";,
 hash = 
"sha256:dcd04f85a29deb80400e8987cfaceb9907140f763453cbffdbd6ff36f1b32c12", size 
= 1502817, upload-time = "2026-05-14T22:43:59.081Z" },
+    { url = 
"https://files.pythonhosted.org/packages/9d/c8/40cb818a08396b1f34d6189c0c42aec917dd331e11fb7c3b870cc61b795a/ast_serialize-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:905fc11940831454d93589bd7ce2acb6a5eb01c2936156f751d2a21087c98cd3", size 
= 1454318, upload-time = "2026-05-14T22:44:01.377Z" },
+    { url = 
"https://files.pythonhosted.org/packages/74/d5/d51494b60cc52f4792be5ddc951631cddb17a2990154634549abdbdbb5bf/ast_serialize-0.4.0-cp314-cp314t-win32.whl";,
 hash = 
"sha256:3bdde2c4570143791f636aed4e3ef868f5b46eb90a18f8d5c41dd045aab08bef", size 
= 1060098, upload-time = "2026-05-14T22:44:03.265Z" },
+    { url = 
"https://files.pythonhosted.org/packages/7a/c9/b0086257c79ff95743a3621448a01fc71b234ae359d3d54cda383aa43939/ast_serialize-0.4.0-cp314-cp314t-win_amd64.whl";,
 hash = 
"sha256:6551d55b8607b97a7755683d743200b398c61a0b71a11b7f00c89c335a11d0f4", size 
= 1101015, upload-time = "2026-05-14T22:44:05.055Z" },
+    { url = 
"https://files.pythonhosted.org/packages/3d/6d/3dfddef4990fda47745af6615a3e51c4de711eda56c3a8072a0d8b6181c7/ast_serialize-0.4.0-cp314-cp314t-win_arm64.whl";,
 hash = 
"sha256:7234ff086cb152ea2a3b7ef895b5ebeb6d80779df049d5c6431c8e3536d5b03c", size 
= 1074495, upload-time = "2026-05-14T22:44:07.186Z" },
+    { url = 
"https://files.pythonhosted.org/packages/be/d5/044c5f995ef75807a0effb56fc288cfdedeeb571222450fb6f7d94fd52f1/ast_serialize-0.4.0-cp39-abi3-macosx_10_12_x86_64.whl";,
 hash = 
"sha256:dcded5056d9f3d201df7833082c07ebcbc566ffc3d4105c9fc9fe278fa086ecb", size 
= 1189800, upload-time = "2026-05-14T22:44:09.333Z" },
+    { url = 
"https://files.pythonhosted.org/packages/a9/5a/52163557789d59a8197c10912ab4a1791c9143731ba0e3d9283ac0791db6/ast_serialize-0.4.0-cp39-abi3-macosx_11_0_arm64.whl";,
 hash = 
"sha256:bd50d201098aae0d202805fe9606c0545492f69a3ec4403337e32c54ad29fc41", size 
= 1181713, upload-time = "2026-05-14T22:44:11.286Z" },
+    { url = 
"https://files.pythonhosted.org/packages/2c/c3/678ce3b6cb594b01c361da87f6c5679d26c1dae1583a082a8cd190e7232e/ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl";,
 hash = 
"sha256:6615b39cd747967c3aabe68bf3f5f26748e823cc6b474ddc1510ed188a824149", size 
= 1243258, upload-time = "2026-05-14T22:44:13.345Z" },
+    { url = 
"https://files.pythonhosted.org/packages/3d/dd/4810fbeb81c47b7e4e65db15ca65c71330efc59b460bd10c12338dc6012e/ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl";,
 hash = 
"sha256:91362c0a9fdf1c344b7f50a5b0508b11a0732102998fbd754a191f7187e77031", size 
= 1239226, upload-time = "2026-05-14T22:44:15.811Z" },
+    { url = 
"https://files.pythonhosted.org/packages/28/38/13a88d90b664c009ed208346ec2ed248b0ab2cb0b582ae467acaa7f44fa4/ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl";,
 hash = 
"sha256:70d9c5d527bbfa69bd3c7d17dac11fb6781e36186a434a06d7d5892e0b2f88f9", size 
= 1448867, upload-time = "2026-05-14T22:44:17.99Z" },
+    { url = 
"https://files.pythonhosted.org/packages/4c/19/a069dba1a634b703bf07fb49df8f7e3c04e9ba8ef3f0d9f4495f72630f92/ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl";,
 hash = 
"sha256:4738790cf54d8b416de992b87ee567056980bc82134d52458bd4985f389d1658", size 
= 1264135, upload-time = "2026-05-14T22:44:19.8Z" },
+    { url = 
"https://files.pythonhosted.org/packages/2d/4c/76ec4279fecd7e78b60c3c99321f944c43cd11e5ff09c952746f5f9c0f4c/ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl";,
 hash = 
"sha256:faa008dccfcb793ae9101325e4d6d026caaa5d845c2182f03749c759834b0a3a", size 
= 1269060, upload-time = "2026-05-14T22:44:21.894Z" },
+    { url = 
"https://files.pythonhosted.org/packages/33/c5/9230ef7481e5cb63b93a1f7738e959586202b081caf32b8bc5d9f673ef56/ast_serialize-0.4.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl";,
 hash = 
"sha256:1c5245228e65d38cb48e1251f0ca71b0fa417e527141491e8c92f740e8e2d121", size 
= 1309654, upload-time = "2026-05-14T22:44:23.725Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b9/54/7d7397528d181ad68e476e0c81aa3ceff7d1f1b5c7fa958d6be28628ef16/ast_serialize-0.4.0-cp39-abi3-musllinux_1_2_aarch64.whl";,
 hash = 
"sha256:8f5153e9c44a02e61f4042c5f9249d2e8a759773d621a0b2f445a899e536e181", size 
= 1418855, upload-time = "2026-05-14T22:44:25.415Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b8/8f/87d6428adaa0986b817404f09329b64f8d2614cfe061ebf4951b4a7e0d19/ast_serialize-0.4.0-cp39-abi3-musllinux_1_2_armv7l.whl";,
 hash = 
"sha256:1e1fb90def261f6a0db885876f7e1a49ad2dbac38ad9f2f62dba2f9543af16e7", size 
= 1516040, upload-time = "2026-05-14T22:44:27.535Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b5/bb/5aaa41a21314c8b0d6dee54867b16535682c6660dd28cac64dba1380062d/ast_serialize-0.4.0-cp39-abi3-musllinux_1_2_i686.whl";,
 hash = 
"sha256:cf2ff7b654c8e95143e20f5d75878cbb78b65b928b26c4d58ef71cdba9d6d981", size 
= 1511450, upload-time = "2026-05-14T22:44:29.522Z" },
+    { url = 
"https://files.pythonhosted.org/packages/87/16/cc729b5bb4b21da99db1379266cc367512e82ba10f9b3300a6f3e9941325/ast_serialize-0.4.0-cp39-abi3-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:90fc5c0d35a22f1a92dd33635508626d50f8fc64deb897c23e78e666a60804c9", size 
= 1463654, upload-time = "2026-05-14T22:44:31.265Z" },
+    { url = 
"https://files.pythonhosted.org/packages/43/97/7198321b0244d011093387b41affea934d58bda08d59a2adfde72976b6c4/ast_serialize-0.4.0-cp39-abi3-win32.whl";,
 hash = 
"sha256:9ecd6a1fc1b86f1f4e8ae206759b6319c10019706b3496b01b54d02b9b2cd918", size 
= 1068636, upload-time = "2026-05-14T22:44:33.189Z" },
+    { url = 
"https://files.pythonhosted.org/packages/10/09/3b868f6d8df4bbe452903a5e0e039ebcec9ea0045f1a77951546205097e8/ast_serialize-0.4.0-cp39-abi3-win_amd64.whl";,
 hash = 
"sha256:79c8d015c771c8bfdb1208003b227b27c40034790a2c29c09f2317a041825ce2", size 
= 1107137, upload-time = "2026-05-14T22:44:35.304Z" },
+    { url = 
"https://files.pythonhosted.org/packages/fd/78/9387dffccdc55a12734f83aaccc4a987404a217a2a12a1920d8d4585950b/ast_serialize-0.4.0-cp39-abi3-win_arm64.whl";,
 hash = 
"sha256:1026f565a7ab846337c630909089b3346a2fe417bf1552b1581ab01852137407", size 
= 1079199, upload-time = "2026-05-14T22:44:36.816Z" },
+]
+
+[[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 = "librt"
+version = "0.11.0"
+source = { registry = "https://pypi.org/simple"; }
+sdist = { url = 
"https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz";,
 hash = 
"sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size 
= 200139, upload-time = "2026-05-10T18:17:25.138Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/fe/87/2bf31fe17587b29e3f93ec31421e2b1e1c3e349b8bf6c7c313dbad1d5340/librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl";,
 hash = 
"sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", size 
= 141092, upload-time = "2026-05-10T18:15:34.795Z" },
+    { url = 
"https://files.pythonhosted.org/packages/cf/08/5c5bf772920b7ebac6e32bc91a643e0ab3870199c0b542356d3baa83970a/librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl";,
 hash = 
"sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", size 
= 142035, upload-time = "2026-05-10T18:15:36.242Z" },
+    { url = 
"https://files.pythonhosted.org/packages/06/20/662a03d254e5b000d838e8b345d83303ddb768c080fd488e40634c0fa66b/librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl";,
 hash = 
"sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", size 
= 475022, upload-time = "2026-05-10T18:15:37.56Z" },
+    { url = 
"https://files.pythonhosted.org/packages/de/f3/aa81523e45184c6ec23dc7f63263362ec55f80a09d424c012359ecbe7e35/librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl";,
 hash = 
"sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", size 
= 467273, upload-time = "2026-05-10T18:15:39.182Z" },
+    { url = 
"https://files.pythonhosted.org/packages/6b/6f/59c74b560ca8853834d5501d589c8a2519f4184f273a085ffd0f37a1cc47/librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl";,
 hash = 
"sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", size 
= 497083, upload-time = "2026-05-10T18:15:40.634Z" },
+    { url = 
"https://files.pythonhosted.org/packages/fe/7b/5aa4d2c9600a719401160bf7055417df0b2a47439b9d88286ce45e56b65f/librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl";,
 hash = 
"sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", size 
= 489139, upload-time = "2026-05-10T18:15:41.934Z" },
+    { url = 
"https://files.pythonhosted.org/packages/d6/31/9143803d7da6856a69153785768c4936864430eec0fd9461c3ea527d9922/librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl";,
 hash = 
"sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", size 
= 508442, upload-time = "2026-05-10T18:15:43.206Z" },
+    { url = 
"https://files.pythonhosted.org/packages/2f/5a/bce08184488426bda4ccc2c4964ac048c8f68ae89bd7120082eef4233cfd/librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl";,
 hash = 
"sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", size 
= 514230, upload-time = "2026-05-10T18:15:44.761Z" },
+    { url = 
"https://files.pythonhosted.org/packages/89/8c/bb5e213d254b7505a0e658da199d8ab719086632ce09eef311ab27976523/librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl";,
 hash = 
"sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", size 
= 494231, upload-time = "2026-05-10T18:15:46.308Z" },
+    { url = 
"https://files.pythonhosted.org/packages/9d/fb/541cdad5b1ab1300398c74c4c9a497b88e5074c21b1244c8f49731d3a284/librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", size 
= 537585, upload-time = "2026-05-10T18:15:47.629Z" },
+    { url = 
"https://files.pythonhosted.org/packages/8f/f2/464bb69295c320cb06bddb4f14a4ec67934ee14b2bffb12b19fb7ab287ba/librt-0.11.0-cp311-cp311-win32.whl";,
 hash = 
"sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", size 
= 100509, upload-time = "2026-05-10T18:15:49.157Z" },
+    { url = 
"https://files.pythonhosted.org/packages/6d/e7/a17ee1788f9e4fbf548c19f4afa07c92089b9e24fef6cb2410863781ef4c/librt-0.11.0-cp311-cp311-win_amd64.whl";,
 hash = 
"sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", size 
= 118628, upload-time = "2026-05-10T18:15:50.345Z" },
+    { url = 
"https://files.pythonhosted.org/packages/cc/c7/6c766214f9f9903bcfcfbef97d807af8d8f5aa3502d247858ab17582d212/librt-0.11.0-cp311-cp311-win_arm64.whl";,
 hash = 
"sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", size 
= 103122, upload-time = "2026-05-10T18:15:52.068Z" },
+    { url = 
"https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl";,
 hash = 
"sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size 
= 144147, upload-time = "2026-05-10T18:15:53.227Z" },
+    { url = 
"https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl";,
 hash = 
"sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size 
= 143614, upload-time = "2026-05-10T18:15:54.657Z" },
+    { url = 
"https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl";,
 hash = 
"sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size 
= 485538, upload-time = "2026-05-10T18:15:56.117Z" },
+    { url = 
"https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl";,
 hash = 
"sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size 
= 479623, upload-time = "2026-05-10T18:15:57.544Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl";,
 hash = 
"sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size 
= 513082, upload-time = "2026-05-10T18:15:58.805Z" },
+    { url = 
"https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl";,
 hash = 
"sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size 
= 508105, upload-time = "2026-05-10T18:16:00.2Z" },
+    { url = 
"https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl";,
 hash = 
"sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size 
= 522268, upload-time = "2026-05-10T18:16:01.708Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl";,
 hash = 
"sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size 
= 527348, upload-time = "2026-05-10T18:16:03.496Z" },
+    { url = 
"https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl";,
 hash = 
"sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size 
= 516294, upload-time = "2026-05-10T18:16:05.173Z" },
+    { url = 
"https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size 
= 553608, upload-time = "2026-05-10T18:16:06.839Z" },
+    { url = 
"https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl";,
 hash = 
"sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size 
= 101879, upload-time = "2026-05-10T18:16:08.103Z" },
+    { url = 
"https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl";,
 hash = 
"sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size 
= 119831, upload-time = "2026-05-10T18:16:09.174Z" },
+    { url = 
"https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl";,
 hash = 
"sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size 
= 103470, upload-time = "2026-05-10T18:16:10.369Z" },
+    { url = 
"https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl";,
 hash = 
"sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size 
= 144119, upload-time = "2026-05-10T18:16:11.771Z" },
+    { url = 
"https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl";,
 hash = 
"sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size 
= 143565, upload-time = "2026-05-10T18:16:13.334Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl";,
 hash = 
"sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size 
= 485395, upload-time = "2026-05-10T18:16:14.729Z" },
+    { url = 
"https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl";,
 hash = 
"sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size 
= 479383, upload-time = "2026-05-10T18:16:16.321Z" },
+    { url = 
"https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl";,
 hash = 
"sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size 
= 513010, upload-time = "2026-05-10T18:16:17.647Z" },
+    { url = 
"https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl";,
 hash = 
"sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size 
= 508433, upload-time = "2026-05-10T18:16:19.309Z" },
+    { url = 
"https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl";,
 hash = 
"sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size 
= 522595, upload-time = "2026-05-10T18:16:20.642Z" },
+    { url = 
"https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl";,
 hash = 
"sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size 
= 527255, upload-time = "2026-05-10T18:16:22.352Z" },
+    { url = 
"https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl";,
 hash = 
"sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size 
= 516847, upload-time = "2026-05-10T18:16:23.627Z" },
+    { url = 
"https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size 
= 553920, upload-time = "2026-05-10T18:16:25.025Z" },
+    { url = 
"https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl";,
 hash = 
"sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size 
= 101898, upload-time = "2026-05-10T18:16:26.649Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl";,
 hash = 
"sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size 
= 119812, upload-time = "2026-05-10T18:16:27.859Z" },
+    { url = 
"https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl";,
 hash = 
"sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size 
= 103448, upload-time = "2026-05-10T18:16:29.066Z" },
+    { url = 
"https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl";,
 hash = 
"sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size 
= 143345, upload-time = "2026-05-10T18:16:30.674Z" },
+    { url = 
"https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl";,
 hash = 
"sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size 
= 143131, upload-time = "2026-05-10T18:16:32.037Z" },
+    { url = 
"https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl";,
 hash = 
"sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size 
= 477024, upload-time = "2026-05-10T18:16:33.493Z" },
+    { url = 
"https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl";,
 hash = 
"sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size 
= 474221, upload-time = "2026-05-10T18:16:34.864Z" },
+    { url = 
"https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl";,
 hash = 
"sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size 
= 505174, upload-time = "2026-05-10T18:16:36.705Z" },
+    { url = 
"https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl";,
 hash = 
"sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size 
= 497216, upload-time = "2026-05-10T18:16:38.418Z" },
+    { url = 
"https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl";,
 hash = 
"sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size 
= 513921, upload-time = "2026-05-10T18:16:39.848Z" },
+    { url = 
"https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl";,
 hash = 
"sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size 
= 520850, upload-time = "2026-05-10T18:16:41.471Z" },
+    { url = 
"https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl";,
 hash = 
"sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size 
= 504237, upload-time = "2026-05-10T18:16:43.15Z" },
+    { url = 
"https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size 
= 546261, upload-time = "2026-05-10T18:16:44.408Z" },
+    { url = 
"https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl";,
 hash = 
"sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size 
= 96965, upload-time = "2026-05-10T18:16:46.039Z" },
+    { url = 
"https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl";,
 hash = 
"sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size 
= 115151, upload-time = "2026-05-10T18:16:47.133Z" },
+    { url = 
"https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl";,
 hash = 
"sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size 
= 98850, upload-time = "2026-05-10T18:16:48.597Z" },
+    { url = 
"https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl";,
 hash = 
"sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size 
= 151138, upload-time = "2026-05-10T18:16:49.839Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl";,
 hash = 
"sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size 
= 151976, upload-time = "2026-05-10T18:16:51.062Z" },
+    { url = 
"https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl";,
 hash = 
"sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size 
= 557927, upload-time = "2026-05-10T18:16:52.632Z" },
+    { url = 
"https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl";,
 hash = 
"sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size 
= 539698, upload-time = "2026-05-10T18:16:53.934Z" },
+    { url = 
"https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl";,
 hash = 
"sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size 
= 577162, upload-time = "2026-05-10T18:16:55.589Z" },
+    { url = 
"https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl";,
 hash = 
"sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size 
= 566494, upload-time = "2026-05-10T18:16:56.975Z" },
+    { url = 
"https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl";,
 hash = 
"sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size 
= 596858, upload-time = "2026-05-10T18:16:58.374Z" },
+    { url = 
"https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl";,
 hash = 
"sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size 
= 590318, upload-time = "2026-05-10T18:16:59.676Z" },
+    { url = 
"https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl";,
 hash = 
"sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size 
= 575115, upload-time = "2026-05-10T18:17:01.007Z" },
+    { url = 
"https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size 
= 617918, upload-time = "2026-05-10T18:17:02.682Z" },
+    { url = 
"https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl";,
 hash = 
"sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size 
= 103562, upload-time = "2026-05-10T18:17:03.99Z" },
+    { url = 
"https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl";,
 hash = 
"sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size 
= 124327, upload-time = "2026-05-10T18:17:05.465Z" },
+    { url = 
"https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl";,
 hash = 
"sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size 
= 102572, upload-time = "2026-05-10T18:17:06.809Z" },
+]
+
+[[package]]
+name = "mypy"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple"; }
+dependencies = [
+    { name = "ast-serialize" },
+    { name = "librt", marker = "platform_python_implementation != 'PyPy'" },
+    { name = "mypy-extensions" },
+    { name = "pathspec" },
+    { name = "typing-extensions" },
+]
+sdist = { url = 
"https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz";,
 hash = 
"sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size 
= 3898359, upload-time = "2026-05-11T18:37:36.237Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl";,
 hash = 
"sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size 
= 14691685, upload-time = "2026-05-11T18:33:27.973Z" },
+    { url = 
"https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl";,
 hash = 
"sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size 
= 13555165, upload-time = "2026-05-11T18:32:16.107Z" },
+    { url = 
"https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl";,
 hash = 
"sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size 
= 13994376, upload-time = "2026-05-11T18:32:39.256Z" },
+    { url = 
"https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl";,
 hash = 
"sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size 
= 14864618, upload-time = "2026-05-11T18:34:49.765Z" },
+    { url = 
"https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size 
= 15102063, upload-time = "2026-05-11T18:34:05.855Z" },
+    { url = 
"https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl";,
 hash = 
"sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size 
= 11060564, upload-time = "2026-05-11T18:35:36.494Z" },
+    { url = 
"https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl";,
 hash = 
"sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size 
= 9966983, upload-time = "2026-05-11T18:37:14.139Z" },
+    { url = 
"https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl";,
 hash = 
"sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size 
= 14874381, upload-time = "2026-05-11T18:37:31.784Z" },
+    { url = 
"https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl";,
 hash = 
"sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size 
= 13665501, upload-time = "2026-05-11T18:34:23.063Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl";,
 hash = 
"sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size 
= 14045750, upload-time = "2026-05-11T18:31:48.151Z" },
+    { url = 
"https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl";,
 hash = 
"sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size 
= 15061630, upload-time = "2026-05-11T18:37:06.898Z" },
+    { url = 
"https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size 
= 15288831, upload-time = "2026-05-11T18:31:18.07Z" },
+    { url = 
"https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl";,
 hash = 
"sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size 
= 11135228, upload-time = "2026-05-11T18:34:31.23Z" },
+    { url = 
"https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl";,
 hash = 
"sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size 
= 10040684, upload-time = "2026-05-11T18:36:48.199Z" },
+    { url = 
"https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl";,
 hash = 
"sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size 
= 14852174, upload-time = "2026-05-11T18:31:38.929Z" },
+    { url = 
"https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl";,
 hash = 
"sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size 
= 13651542, upload-time = "2026-05-11T18:36:04.636Z" },
+    { url = 
"https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl";,
 hash = 
"sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size 
= 14033929, upload-time = "2026-05-11T18:35:55.742Z" },
+    { url = 
"https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl";,
 hash = 
"sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size 
= 15039200, upload-time = "2026-05-11T18:33:10.281Z" },
+    { url = 
"https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size 
= 15272690, upload-time = "2026-05-11T18:32:07.205Z" },
+    { url = 
"https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl";,
 hash = 
"sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size 
= 11147435, upload-time = "2026-05-11T18:33:56.477Z" },
+    { url = 
"https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl";,
 hash = 
"sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size 
= 10035052, upload-time = "2026-05-11T18:32:30.049Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl";,
 hash = 
"sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size 
= 14848422, upload-time = "2026-05-11T18:35:45.984Z" },
+    { url = 
"https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl";,
 hash = 
"sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size 
= 13677374, upload-time = "2026-05-11T18:36:57.188Z" },
+    { url = 
"https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl";,
 hash = 
"sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size 
= 14055743, upload-time = "2026-05-11T18:35:18.361Z" },
+    { url = 
"https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl";,
 hash = 
"sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size 
= 15020937, upload-time = "2026-05-11T18:34:59.618Z" },
+    { url = 
"https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size 
= 15253371, upload-time = "2026-05-11T18:36:41.081Z" },
+    { url = 
"https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl";,
 hash = 
"sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size 
= 11326429, upload-time = "2026-05-11T18:34:13.526Z" },
+    { url = 
"https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl";,
 hash = 
"sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size 
= 10218799, upload-time = "2026-05-11T18:32:23.491Z" },
+    { url = 
"https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl";,
 hash = 
"sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size 
= 15923458, upload-time = "2026-05-11T18:35:28.64Z" },
+    { url = 
"https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl";,
 hash = 
"sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size 
= 14757697, upload-time = "2026-05-11T18:36:14.208Z" },
+    { url = 
"https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl";,
 hash = 
"sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size 
= 15405638, upload-time = "2026-05-11T18:33:48.249Z" },
+    { url = 
"https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl";,
 hash = 
"sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size 
= 16215852, upload-time = "2026-05-11T18:32:50.296Z" },
+    { url = 
"https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size 
= 16452695, upload-time = "2026-05-11T18:33:38.182Z" },
+    { url = 
"https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl";,
 hash = 
"sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size 
= 12866622, upload-time = "2026-05-11T18:34:39.945Z" },
+    { url = 
"https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl";,
 hash = 
"sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size 
= 10610798, upload-time = "2026-05-11T18:36:31.444Z" },
+    { url = 
"https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl";,
 hash = 
"sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size 
= 2693302, upload-time = "2026-05-11T18:31:29.246Z" },
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple"; }
+sdist = { url = 
"https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz";,
 hash = 
"sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size 
= 6343, upload-time = "2025-04-22T14:54:24.164Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl";,
 hash = 
"sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size 
= 4963, upload-time = "2025-04-22T14:54:22.983Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.2"
+source = { registry = "https://pypi.org/simple"; }
+sdist = { url = 
"https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz";,
 hash = 
"sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size 
= 228134, upload-time = "2026-04-24T20:15:23.917Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl";,
 hash = 
"sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size 
= 100195, upload-time = "2026-04-24T20:15:22.081Z" },
+]
+
+[[package]]
+name = "pathspec"
+version = "1.1.1"
+source = { registry = "https://pypi.org/simple"; }
+sdist = { url = 
"https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz";,
 hash = 
"sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size 
= 135180, upload-time = "2026-04-27T01:46:08.907Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl";,
 hash = 
"sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size 
= 57328, upload-time = "2026-04-27T01:46:07.06Z" },
+]
+
+[[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.20.0"
+source = { registry = "https://pypi.org/simple"; }
+sdist = { url = 
"https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz";,
 hash = 
"sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size 
= 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl";,
 hash = 
"sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size 
= 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "9.0.3"
+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/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz";,
 hash = 
"sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size 
= 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
+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 = "ruff"
+version = "0.15.13"
+source = { registry = "https://pypi.org/simple"; }
+sdist = { url = 
"https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz";,
 hash = 
"sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size 
= 4678180, upload-time = "2026-05-14T13:44:37.869Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl";,
 hash = 
"sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size 
= 10738279, upload-time = "2026-05-14T13:44:18.7Z" },
+    { url = 
"https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl";,
 hash = 
"sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size 
= 11124798, upload-time = "2026-05-14T13:44:06.427Z" },
+    { url = 
"https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl";,
 hash = 
"sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size 
= 10460761, upload-time = "2026-05-14T13:44:04.375Z" },
+    { url = 
"https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl";,
 hash = 
"sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size 
= 10804451, upload-time = "2026-05-14T13:44:25.221Z" },
+    { url = 
"https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl";,
 hash = 
"sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size 
= 10534285, upload-time = "2026-05-14T13:44:08.888Z" },
+    { url = 
"https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl";,
 hash = 
"sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size 
= 11312063, upload-time = "2026-05-14T13:44:11.274Z" },
+    { url = 
"https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl";,
 hash = 
"sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size 
= 12183079, upload-time = "2026-05-14T13:44:01.634Z" },
+    { url = 
"https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl";,
 hash = 
"sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size 
= 11440833, upload-time = "2026-05-14T13:43:59.043Z" },
+    { url = 
"https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl";,
 hash = 
"sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size 
= 11434486, upload-time = "2026-05-14T13:44:27.761Z" },
+    { url = 
"https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl";,
 hash = 
"sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size 
= 11385189, upload-time = "2026-05-14T13:44:13.704Z" },
+    { url = 
"https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl";,
 hash = 
"sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size 
= 10781380, upload-time = "2026-05-14T13:43:56.734Z" },
+    { url = 
"https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl";,
 hash = 
"sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size 
= 10540605, upload-time = "2026-05-14T13:44:20.748Z" },
+    { url = 
"https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl";,
 hash = 
"sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size 
= 11036554, upload-time = "2026-05-14T13:44:16.256Z" },
+    { url = 
"https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl";,
 hash = 
"sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size 
= 11528133, upload-time = "2026-05-14T13:44:22.808Z" },
+    { url = 
"https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl";,
 hash = 
"sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size 
= 10721455, upload-time = "2026-05-14T13:44:35.697Z" },
+    { url = 
"https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl";,
 hash = 
"sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size 
= 11900409, upload-time = "2026-05-14T13:44:30.389Z" },
+    { url = 
"https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl";,
 hash = 
"sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size 
= 11179336, upload-time = "2026-05-14T13:44:33.026Z" },
+]
+
+[[package]]
+name = "spec-status-index"
+version = "0.1.0"
+source = { editable = "." }
+
+[package.dev-dependencies]
+dev = [
+    { name = "mypy" },
+    { name = "pytest" },
+    { name = "ruff" },
+]
+
+[package.metadata]
+
+[package.metadata.requires-dev]
+dev = [
+    { name = "mypy", specifier = ">=1.11" },
+    { name = "pytest", specifier = ">=8.0" },
+    { name = "ruff", specifier = ">=0.6" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple"; }
+sdist = { url = 
"https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz";,
 hash = 
"sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size 
= 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl";,
 hash = 
"sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size 
= 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]

Reply via email to