From 5911d6a41fe65046c7fed9f9d63cfc522c26b6e0 Mon Sep 17 00:00:00 2001 From: yukaty <254470+yukaty@users.noreply.github.com> Date: Mon, 17 Aug 2026 06:58:40 -0600 Subject: [PATCH 1/2] Add TDD with AI agents tutorial --- tdd-ai-agents/.claude/settings.json | 15 ++++++++ tdd-ai-agents/.gitignore | 13 +++++++ tdd-ai-agents/pyproject.toml | 7 ++++ tdd-ai-agents/tests/test_core.py | 46 ++++++++++++++++++++++++ tdd-ai-agents/version_check/__init__.py | 3 ++ tdd-ai-agents/version_check/core.py | 48 +++++++++++++++++++++++++ 6 files changed, 132 insertions(+) create mode 100644 tdd-ai-agents/.claude/settings.json create mode 100644 tdd-ai-agents/.gitignore create mode 100644 tdd-ai-agents/pyproject.toml create mode 100644 tdd-ai-agents/tests/test_core.py create mode 100644 tdd-ai-agents/version_check/__init__.py create mode 100644 tdd-ai-agents/version_check/core.py diff --git a/tdd-ai-agents/.claude/settings.json b/tdd-ai-agents/.claude/settings.json new file mode 100644 index 0000000000..9e4680d640 --- /dev/null +++ b/tdd-ai-agents/.claude/settings.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "jq -e '.tool_input.file_path | contains(\"/tests/\")' >/dev/null && echo 'tests/ is read-only during implementation' >&2 && exit 2 || exit 0" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/tdd-ai-agents/.gitignore b/tdd-ai-agents/.gitignore new file mode 100644 index 0000000000..38876dcbd2 --- /dev/null +++ b/tdd-ai-agents/.gitignore @@ -0,0 +1,13 @@ +# Virtual environment +venv/ +.venv/ + +# Python cache +__pycache__/ +*.py[cod] + +# Testing +.pytest_cache/ + +# OS files +.DS_Store diff --git a/tdd-ai-agents/pyproject.toml b/tdd-ai-agents/pyproject.toml new file mode 100644 index 0000000000..20fb19b765 --- /dev/null +++ b/tdd-ai-agents/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "version-check" +version = "0.1.0" + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" diff --git a/tdd-ai-agents/tests/test_core.py b/tdd-ai-agents/tests/test_core.py new file mode 100644 index 0000000000..790a35db94 --- /dev/null +++ b/tdd-ai-agents/tests/test_core.py @@ -0,0 +1,46 @@ +import pytest +from version_check import Version + + +def test_equal_versions_compare_equal(): + assert Version("1.2.0") == Version("1.2.0") + + +def test_smaller_release_sorts_first(): + assert Version("1.2.0") < Version("1.3.0") + + +def test_double_digit_segments_sort_numerically(): + assert Version("3.9") < Version("3.10") + + +@pytest.mark.parametrize( + "lower, higher", + [ + ("1.0.dev1", "1.0"), # Dev before final + ("1.0a1", "1.0"), # Pre-release before final + ("1.0", "1.0.post1"), # Post-release after final + ("1.0.dev1", "1.0a1"), # Dev before pre-release + ("1.0a1", "1.0b1"), # Alpha before beta + ("1.0b1", "1.0rc1"), # Beta before release candidate + ("1.0.dev1", "1.0.post1"), # Dev before post, spanning the release + ], +) +def test_phase_ordering(lower, higher): + assert Version(lower) < Version(higher) + + +@pytest.mark.parametrize( + "text", + [ + "", # Empty string + " ", # Whitespace only + "1.x.0", # Non-numeric segment + "banana", # Not a version at all + "1..0", # Empty segment + "v1.0", # Leading junk + ], +) +def test_invalid_input_raises(text): + with pytest.raises(ValueError): + Version(text) diff --git a/tdd-ai-agents/version_check/__init__.py b/tdd-ai-agents/version_check/__init__.py new file mode 100644 index 0000000000..ec4fbc4920 --- /dev/null +++ b/tdd-ai-agents/version_check/__init__.py @@ -0,0 +1,3 @@ +from version_check.core import Version + +__all__ = ["Version"] diff --git a/tdd-ai-agents/version_check/core.py b/tdd-ai-agents/version_check/core.py new file mode 100644 index 0000000000..c0f1724006 --- /dev/null +++ b/tdd-ai-agents/version_check/core.py @@ -0,0 +1,48 @@ +import re +from functools import total_ordering + +_VERSION = re.compile( + r"^\s*(?P\d+(?:\.\d+)*)" + r"(?:(?Pa|b|rc)(?P\d+))?" + r"(?:\.dev(?P\d+))?" + r"(?:\.post(?P\d+))?\s*$" +) +_PRE_ORDER = {"a": 0, "b": 1, "rc": 2} + + +@total_ordering +class Version: + def __init__(self, text): + match = _VERSION.match(text) + if match is None: + raise ValueError(f"invalid version string: {text!r}") + self._release = tuple( + int(part) for part in match["release"].split(".") + ) + if match["pre_label"] is not None: + self._pre = (_PRE_ORDER[match["pre_label"]], int(match["pre_num"])) + else: + self._pre = None + self._dev = int(match["dev"]) if match["dev"] else None + self._post = int(match["post"]) if match["post"] else None + + def _key(self): + if self._dev is not None and self._pre is None and self._post is None: + phase = (0, self._dev) + elif self._pre is not None: + phase = (1, self._pre) + elif self._post is not None: + phase = (3, self._post) + else: + phase = (2, 0) + return (self._release, phase) + + def __eq__(self, other): + if not isinstance(other, Version): + return NotImplemented + return self._key() == other._key() + + def __lt__(self, other): + if not isinstance(other, Version): + return NotImplemented + return self._key() < other._key() From 5529179a2a26352ecade83bd4fd46b00955e0894 Mon Sep 17 00:00:00 2001 From: yukaty <254470+yukaty@users.noreply.github.com> Date: Mon, 17 Aug 2026 08:47:02 -0600 Subject: [PATCH 2/2] Add README --- tdd-ai-agents/README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tdd-ai-agents/README.md diff --git a/tdd-ai-agents/README.md b/tdd-ai-agents/README.md new file mode 100644 index 0000000000..5df6af529f --- /dev/null +++ b/tdd-ai-agents/README.md @@ -0,0 +1,2 @@ +# TDD With AI Agents: Design the Tests First, Let the Agent Implement +This folder contains sample code for the Real Python tutorial [TDD With AI Agents: Design the Tests First, Let the Agent Implement].