DEVX-118: feat: enhance documentation-as-code with badges, version refs, Vale
Post-merge / detect-type (push) Successful in 11s
Post-merge / validate-commit-msg (push) Successful in 13s
Post-merge / vikunja (push) Successful in 19s
Post-merge / configure-repo (push) Successful in 15s
Post-merge / release (push) Successful in 45s
Post-merge / sync-wiki (push) Successful in 50s
Post-merge / publish (push) Successful in 32s
Post-merge / badges (push) Failing after 36s

- Fix badge system: clean .badges dir from orphan branch, add version
  verification, make badges job depend on release (avoids stale version
  badge race condition)
- Add check_doc_versions.py: lint tool that verifies docs version
  references match current __version__, with --fix for auto-update
- Integrate check_doc_versions into release process (auto-updates docs
  on every release commit)
- Add Vale prose linter integration: .vale.ini, custom styles for
  terminology and code block language, CI step, make target
- Fix stale version references in docs (0.27.0 → 0.33.4)
- Fix e.g. → for example in docs (Google.Latin Vale rule)
- Add CI steps for check_doc_versions and Vale to quality workflow
- Add make targets: devx-check-doc-versions, devx-vale

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
emil
2026-07-06 10:03:47 +02:00
co-authored by Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 3e12cf222f
commit bbf0c81c32
74 changed files with 2748 additions and 32 deletions
+255
View File
@@ -0,0 +1,255 @@
"""Tests for devx.tools.check_doc_versions."""
from __future__ import annotations
from pathlib import Path
from click.testing import CliRunner
import devx.tools.check_doc_versions as cdv
class TestDetectPackageName:
def test_finds_package(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "myproj"
src.mkdir(parents=True)
(src / "__init__.py").write_text('__version__ = "1.0.0"\n')
assert cdv.detect_package_name(tmp_path) == "myproj"
def test_no_src_dir(self, tmp_path: Path) -> None:
assert cdv.detect_package_name(tmp_path) is None
def test_no_init_py(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "myproj"
src.mkdir(parents=True)
assert cdv.detect_package_name(tmp_path) is None
class TestReadVersion:
def test_reads_version(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "myproj"
src.mkdir(parents=True)
(src / "__init__.py").write_text('__version__ = "2.3.4"\n')
assert cdv.read_version(tmp_path, "myproj") == "2.3.4"
def test_no_version(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "myproj"
src.mkdir(parents=True)
(src / "__init__.py").write_text("# no version here\n")
assert cdv.read_version(tmp_path, "myproj") is None
def test_no_init_file(self, tmp_path: Path) -> None:
assert cdv.read_version(tmp_path, "nonexistent") is None
class TestFindVersionRefs:
def test_finds_gte_ref(self) -> None:
content = ' "devx>=0.27.0",\n'
refs = cdv.find_version_refs(content, "devx")
assert len(refs) == 1
_, full, op, ver, _ = refs[0]
assert op == ">="
assert ver == "0.27.0"
def test_finds_eq_ref(self) -> None:
content = '"devx==0.33.4"'
refs = cdv.find_version_refs(content, "devx")
assert len(refs) == 1
_, _, op, ver, _ = refs[0]
assert op == "=="
assert ver == "0.33.4"
def test_finds_extras_ref(self) -> None:
content = '"devx[dev]>=0.27.0"'
refs = cdv.find_version_refs(content, "devx")
assert len(refs) == 1
_, _, op, ver, _ = refs[0]
assert op == ">="
assert ver == "0.27.0"
def test_finds_upper_bound(self) -> None:
content = '"devx>=0.27.0,<0.28"'
refs = cdv.find_version_refs(content, "devx")
assert len(refs) == 1
_, _, _, _, rest = refs[0]
assert "<0.28" in rest
def test_ignores_other_packages(self) -> None:
content = '"other-pkg>=1.0.0"'
refs = cdv.find_version_refs(content, "devx")
assert len(refs) == 0
def test_multiple_refs(self) -> None:
content = '"devx>=0.27.0"\n"devx==0.33.4"\n'
refs = cdv.find_version_refs(content, "devx")
assert len(refs) == 2
class TestFixVersionRefs:
def test_fixes_stale_version(self) -> None:
content = '"devx>=0.27.0"'
new, fixes = cdv.fix_version_refs(content, "devx", "0.33.4")
assert fixes == 1
assert "0.33.4" in new
assert "0.27.0" not in new
def test_no_fix_needed(self) -> None:
content = '"devx>=0.33.4"'
new, fixes = cdv.fix_version_refs(content, "devx", "0.33.4")
assert fixes == 0
assert new == content
def test_fixes_upper_bound(self) -> None:
content = '"devx>=0.27.0,<0.28"'
new, fixes = cdv.fix_version_refs(content, "devx", "0.33.4")
assert fixes == 1
assert "0.33.4" in new
assert "<0.34" in new
assert "<0.28" not in new
def test_ignores_other_packages(self) -> None:
content = '"other>=1.0.0"'
new, fixes = cdv.fix_version_refs(content, "devx", "0.33.4")
assert fixes == 0
assert new == content
class TestMain:
def test_pass_when_current(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "devx"
src.mkdir(parents=True)
(src / "__init__.py").write_text('__version__ = "0.33.4"\n')
readme = tmp_path / "README.md"
readme.write_text('"devx>=0.33.4"\n')
runner = CliRunner()
result = runner.invoke(cdv.main, ["--root", str(tmp_path)])
assert result.exit_code == 0
assert "PASS" in result.output
def test_fail_when_stale(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "devx"
src.mkdir(parents=True)
(src / "__init__.py").write_text('__version__ = "0.33.4"\n')
readme = tmp_path / "README.md"
readme.write_text('"devx>=0.27.0"\n')
runner = CliRunner()
result = runner.invoke(cdv.main, ["--root", str(tmp_path)])
assert result.exit_code == 1
assert "stale" in result.output
def test_fix_updates_files(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "devx"
src.mkdir(parents=True)
(src / "__init__.py").write_text('__version__ = "0.33.4"\n')
readme = tmp_path / "README.md"
readme.write_text('"devx>=0.27.0"\n')
runner = CliRunner()
result = runner.invoke(cdv.main, ["--root", str(tmp_path), "--fix"])
assert result.exit_code == 0
assert "0.33.4" in readme.read_text()
def test_no_package_skips(self, tmp_path: Path) -> None:
runner = CliRunner()
result = runner.invoke(cdv.main, ["--root", str(tmp_path)])
assert result.exit_code == 0
assert "skipping" in result.output
def test_no_version_skips(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "devx"
src.mkdir(parents=True)
(src / "__init__.py").write_text("# no version\n")
runner = CliRunner()
result = runner.invoke(cdv.main, ["--root", str(tmp_path)])
assert result.exit_code == 0
assert "Cannot read" in result.output
def test_docs_only_skips_readme(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "devx"
src.mkdir(parents=True)
(src / "__init__.py").write_text('__version__ = "0.33.4"\n')
readme = tmp_path / "README.md"
readme.write_text('"devx>=0.27.0"\n')
docs = tmp_path / "docs"
docs.mkdir()
(docs / "index.md").write_text('"devx>=0.33.4"\n')
runner = CliRunner()
result = runner.invoke(cdv.main, ["--root", str(tmp_path), "--docs-only"])
assert result.exit_code == 0
assert "PASS" in result.output
def test_fix_no_stale(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "devx"
src.mkdir(parents=True)
(src / "__init__.py").write_text('__version__ = "0.33.4"\n')
readme = tmp_path / "README.md"
readme.write_text('"devx>=0.33.4"\n')
runner = CliRunner()
result = runner.invoke(cdv.main, ["--root", str(tmp_path), "--fix"])
assert result.exit_code == 0
assert "No stale" in result.output
def test_checks_docs_dir(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "devx"
src.mkdir(parents=True)
(src / "__init__.py").write_text('__version__ = "0.33.4"\n')
docs = tmp_path / "docs"
docs.mkdir()
(docs / "index.md").write_text('"devx>=0.27.0"\n')
runner = CliRunner()
result = runner.invoke(cdv.main, ["--root", str(tmp_path)])
assert result.exit_code == 1
assert "docs/index.md" in result.output
def test_detect_package_with_non_dir_entry(self, tmp_path: Path) -> None:
src = tmp_path / "src"
src.mkdir(parents=True)
# `aaa_file.py` sorts before `devx/` so the non-dir branch is hit
(src / "aaa_file.py").touch()
pkg_dir = src / "devx"
pkg_dir.mkdir()
(pkg_dir / "__init__.py").write_text('__version__ = "1.0.0"\n')
assert cdv.detect_package_name(tmp_path) == "devx"
def test_read_version_auto_detect(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "devx"
src.mkdir(parents=True)
(src / "__init__.py").write_text('__version__ = "3.2.1"\n')
assert cdv.read_version(tmp_path) == "3.2.1"
def test_read_version_no_package(self, tmp_path: Path) -> None:
assert cdv.read_version(tmp_path) is None
def test_main_with_file_without_refs(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "devx"
src.mkdir(parents=True)
(src / "__init__.py").write_text('__version__ = "0.33.4"\n')
docs = tmp_path / "docs"
docs.mkdir()
(docs / "index.md").write_text("# No version refs here\n")
(docs / "other.md").write_text('"devx>=0.27.0"\n')
runner = CliRunner()
result = runner.invoke(cdv.main, ["--root", str(tmp_path)])
assert result.exit_code == 1
assert "other.md" in result.output
def test_fix_with_file_without_refs(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "devx"
src.mkdir(parents=True)
(src / "__init__.py").write_text('__version__ = "0.33.4"\n')
readme = tmp_path / "README.md"
readme.write_text("# No refs\n")
runner = CliRunner()
result = runner.invoke(cdv.main, ["--root", str(tmp_path), "--fix"])
assert result.exit_code == 0
assert "No stale" in result.output
def test_fix_with_current_refs(self, tmp_path: Path) -> None:
src = tmp_path / "src" / "devx"
src.mkdir(parents=True)
(src / "__init__.py").write_text('__version__ = "0.33.4"\n')
readme = tmp_path / "README.md"
readme.write_text('"devx>=0.33.4"\n')
runner = CliRunner()
result = runner.invoke(cdv.main, ["--root", str(tmp_path), "--fix"])
assert result.exit_code == 0
assert "No stale" in result.output
+34 -1
View File
@@ -269,6 +269,34 @@ class TestInstallTofu:
assert (tmp_path / "tofu").exists()
class TestInstallVale:
def test_already_installed(self) -> None:
with patch.object(install_tools, "_is_installed", return_value=True):
assert install_tools.install_vale() is True
def test_install(self, tmp_path: Path) -> None:
import io
import tarfile
tarball_path = tmp_path / "archive.tar.gz"
binary_content = b"fake vale"
with tarfile.open(tarball_path, "w:gz") as tar:
info = tarfile.TarInfo(name="vale")
info.size = len(binary_content)
tar.addfile(info, io.BytesIO(binary_content))
with patch.object(install_tools, "_is_installed", return_value=False):
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch.object(platform, "machine", return_value="x86_64"):
with patch.object(
install_tools,
"_download",
side_effect=lambda url, dest: Path(dest).write_bytes(tarball_path.read_bytes()),
):
assert install_tools.install_vale() is True
assert (tmp_path / "vale").exists()
class TestListTools:
def test_list(self, tmp_path: Path) -> None:
with patch.object(install_tools, "TARGET_DIR", tmp_path):
@@ -308,6 +336,11 @@ class TestInstallTool:
assert install_tools._install_tool("tofu") is True
mock.assert_called_once()
def test_vale(self) -> None:
with patch.object(install_tools, "install_vale", return_value=True) as mock:
assert install_tools._install_tool("vale") is True
mock.assert_called_once()
def test_unknown_tool(self) -> None:
with pytest.raises(ClickException, match="Unknown tool"):
install_tools._install_tool("unknown")
@@ -326,7 +359,7 @@ class TestMain:
with patch.object(install_tools, "_install_tool", return_value=True) as mock_install:
result = runner.invoke(install_tools.main, [])
assert result.exit_code == 0
assert mock_install.call_count == 6
assert mock_install.call_count == 7
def test_install_specific_tool(self) -> None:
runner = CliRunner()
+37 -1
View File
@@ -68,10 +68,12 @@ class TestPushToBadgesBranch:
sha_result = MagicMock()
sha_result.stdout = "abc123\n"
diff_result = MagicMock()
diff_result.stdout = "coverage.svg\n"
default_result = MagicMock()
with patch(
"subprocess.run",
side_effect=[default_result] * 7 + [sha_result],
side_effect=[default_result] * 6 + [diff_result] + [default_result, default_result, sha_result],
) as mock_run:
sha = push_badges.push_to_badges_branch(str(badges_dir))
@@ -143,6 +145,40 @@ class TestUpdateReadmeWithBadgeSha:
push_badges.update_readme_with_badge_sha("abc123def456", repo_root=tmp_path)
# Should not raise
def test_version_verification_stale(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
readme = tmp_path / "README.md"
readme.write_text("[![Tests](https://git.oblachno.oblachno.fyi/my-org/my-repo/raw/branch/badges/tests.svg)]")
monkeypatch.chdir(tmp_path)
badges_dir = tmp_path / ".badges"
badges_dir.mkdir(exist_ok=True)
(badges_dir / "version.svg").write_text("version: v0.27.0")
with patch("subprocess.run"):
with patch("devx.tools.generate_badges.detect_package_name", return_value="devx"):
with patch("devx.tools.generate_badges.read_version", return_value="0.33.4"):
push_badges.update_readme_with_badge_sha("abc123def456", repo_root=tmp_path)
def test_version_verification_current(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
readme = tmp_path / "README.md"
readme.write_text("[![Tests](https://git.oblachno.oblachno.fyi/my-org/my-repo/raw/branch/badges/tests.svg)]")
monkeypatch.chdir(tmp_path)
badges_dir = tmp_path / ".badges"
badges_dir.mkdir(exist_ok=True)
(badges_dir / "version.svg").write_text("version: v0.33.4")
with patch("subprocess.run"):
with patch("devx.tools.generate_badges.detect_package_name", return_value="devx"):
with patch("devx.tools.generate_badges.read_version", return_value="0.33.4"):
push_badges.update_readme_with_badge_sha("abc123def456", repo_root=tmp_path)
def test_version_verification_no_badges_dir(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
readme = tmp_path / "README.md"
readme.write_text("[![Tests](https://git.oblachno.oblachno.fyi/my-org/my-repo/raw/branch/badges/tests.svg)]")
monkeypatch.chdir(tmp_path)
# No .badges/version.svg exists — should skip verification gracefully
with patch("subprocess.run"):
with patch("devx.tools.generate_badges.detect_package_name", return_value="devx"):
with patch("devx.tools.generate_badges.read_version", return_value="0.33.4"):
push_badges.update_readme_with_badge_sha("abc123def456", repo_root=tmp_path)
class TestMain:
def test_success(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
+17 -1
View File
@@ -27,6 +27,7 @@ from devx.ci.release import (
run_tests,
tag_exists,
update_changelog,
update_doc_versions,
update_init_version,
verify_alignment,
verify_tag_consistency,
@@ -801,7 +802,7 @@ class TestCommitReleaseChanges:
result = commit_release_changes("0.2.0")
assert result is True
calls = [c.args[0] for c in mock_run_cmd.call_args_list]
assert ["git", "add", "src/devx/__init__.py", "CHANGELOG.md"] in calls
assert ["git", "add", "src/devx/__init__.py", "CHANGELOG.md", "README.md", "docs/"] in calls
assert ["git", "commit", "--no-verify", "-m", "release: v0.2.0 [skip ci]"] in calls
@patch("devx.ci.release.run_cmd")
@@ -814,6 +815,21 @@ class TestCommitReleaseChanges:
assert ["git", "commit", "--no-verify", "-m", "release: v0.1.0 [skip ci]"] not in calls
class TestUpdateDocVersions:
@patch("subprocess.run")
def test_success(self, mock_run: MagicMock) -> None:
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
update_doc_versions("0.33.4")
assert mock_run.called
@patch("subprocess.run")
def test_failure_warns(self, mock_run: MagicMock) -> None:
mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="some error")
# Should not raise
update_doc_versions("0.33.4")
assert mock_run.called
class TestCreateAndPushTag:
@patch("devx.ci.release.tag_exists", return_value=False)
@patch("devx.ci.release.run_cmd")