diff --git a/CHANGELOG.md b/CHANGELOG.md index af348d4..f886b18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- `ossmate version` and `ossmate_mcp.__version__` resolved to the hardcoded literal `"0.0.1"` instead of the installed package version. Both `__init__.py` modules now read from `importlib.metadata.version()` so they always agree with what `pip show` reports. New invariant test [tests/test_versioning.py](tests/test_versioning.py)::`test_init_modules_dont_hardcode_versions` forbids the hardcoded form going forward — caught the day after v0.1.0 shipped to PyPI + ## [0.1.0] - 2026-04-19 First public release. Reference implementation of every Claude Code extension surface, packaged as both a plugin and a standalone CLI. diff --git a/cli/ossmate/src/ossmate/__init__.py b/cli/ossmate/src/ossmate/__init__.py index 5b81ee4..e829409 100644 --- a/cli/ossmate/src/ossmate/__init__.py +++ b/cli/ossmate/src/ossmate/__init__.py @@ -1,3 +1,8 @@ """Ossmate — Claude-powered co-maintainer CLI.""" -__version__ = "0.0.1" +from importlib.metadata import PackageNotFoundError, version as _pkg_version + +try: + __version__ = _pkg_version("ossmate") +except PackageNotFoundError: + __version__ = "0.0.0+source" diff --git a/mcp/ossmate_mcp/src/ossmate_mcp/__init__.py b/mcp/ossmate_mcp/src/ossmate_mcp/__init__.py index ff6c0cd..bcbe141 100644 --- a/mcp/ossmate_mcp/src/ossmate_mcp/__init__.py +++ b/mcp/ossmate_mcp/src/ossmate_mcp/__init__.py @@ -1,3 +1,8 @@ """Ossmate MCP server — OSS maintainer tools exposed via Model Context Protocol.""" -__version__ = "0.0.1" +from importlib.metadata import PackageNotFoundError, version as _pkg_version + +try: + __version__ = _pkg_version("ossmate-mcp") +except PackageNotFoundError: + __version__ = "0.0.0+source" diff --git a/tests/test_versioning.py b/tests/test_versioning.py index 573d268..5d52d9a 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -115,6 +115,28 @@ def test_marketplace_manifest_has_two_version_locations(self): assert "metadata" in data and "version" in data["metadata"] assert data["plugins"] and "version" in data["plugins"][0] + def test_init_modules_dont_hardcode_versions(self): + """`__init__.py` files MUST resolve __version__ from importlib.metadata + rather than hardcoding the literal — otherwise `bump_version.py` + misses them and `ossmate version` lies to users (regression caught + in v0.1.0 post-release).""" + for init in ( + REPO_ROOT / "cli" / "ossmate" / "src" / "ossmate" / "__init__.py", + REPO_ROOT / "mcp" / "ossmate_mcp" / "src" / "ossmate_mcp" / "__init__.py", + ): + text = init.read_text(encoding="utf-8") + assert "importlib.metadata" in text, ( + f"{init.relative_to(REPO_ROOT)} must read __version__ from " + f"importlib.metadata — hardcoding drifts on every release" + ) + # Belt-and-suspenders: forbid the literal `__version__ = "X.Y.Z"` form. + import re as _re + + assert not _re.search(r'^__version__\s*=\s*"\d', text, _re.MULTILINE), ( + f"{init.relative_to(REPO_ROOT)} hardcodes __version__ — use " + f"importlib.metadata.version() instead" + ) + def test_cli_dep_pin_matches_mcp_version(self, bump): """The CLI declares `ossmate-mcp>=X` — X must equal the MCP package version, otherwise users get an unsolvable resolver state on first