From 47ac329947a903ec77b65aa0eb021495f098b02a Mon Sep 17 00:00:00 2001 From: Andraxion Date: Wed, 29 Jul 2026 15:42:45 -0400 Subject: [PATCH] Centralize release version identity --- LICENSE | 21 +++++++++++++++++++++ pyproject.toml | 5 ++++- src/docforge/__init__.py | 3 ++- src/docforge/_version.py | 3 +++ src/docforge/cli.py | 2 ++ src/docforge/mcp_server.py | 4 +++- src/docforge/reference_mcp.py | 2 ++ src/docforge/viewer_manager.py | 2 ++ tests/test_public_contract.py | 26 ++++++++++++++++++++++++-- uv.lock | 3 +-- 10 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 LICENSE create mode 100644 src/docforge/_version.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..948b6e6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Worldforge contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pyproject.toml b/pyproject.toml index e3c16be..64c98cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "docforge" -version = "1.3.0.dev0" +dynamic = ["version"] description = "Project-scoped documentation indexing and context service" readme = "README.md" requires-python = ">=3.12" @@ -57,6 +57,9 @@ packages = ["src/docforge", "src/docforge_renderers"] [tool.hatch.build.targets.wheel.force-include] schemas = "docforge/schemas" +[tool.hatch.version] +path = "src/docforge/_version.py" + [tool.ruff] line-length = 100 target-version = "py312" diff --git a/src/docforge/__init__.py b/src/docforge/__init__.py index df4e057..defc8a5 100644 --- a/src/docforge/__init__.py +++ b/src/docforge/__init__.py @@ -1,5 +1,6 @@ """Project-scoped documentation retrieval, proposals, and gated application.""" +from ._version import __version__ from .application import CanonicalApplicationService, CanonicalApplier, GenericCanonicalApplier from .errors import DocForgeError from .project import Project @@ -10,5 +11,5 @@ __all__ = [ "DocForgeError", "GenericCanonicalApplier", "Project", + "__version__", ] -__version__ = "1.3.0.dev0" diff --git a/src/docforge/_version.py b/src/docforge/_version.py new file mode 100644 index 0000000..c0e966c --- /dev/null +++ b/src/docforge/_version.py @@ -0,0 +1,3 @@ +"""Single authoritative DocForge distribution and runtime version.""" + +__version__ = "1.4.0" diff --git a/src/docforge/cli.py b/src/docforge/cli.py index 9e5f507..1030fed 100644 --- a/src/docforge/cli.py +++ b/src/docforge/cli.py @@ -8,6 +8,7 @@ import sys import webbrowser from pathlib import Path +from ._version import __version__ from .application import CanonicalApplicationService, GenericCanonicalApplier from .client_config import CLIENT_NAMES, generate_client_configuration from .context import compile_context @@ -25,6 +26,7 @@ from .viewer_manager import ViewerManagerClient def _parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(prog="docforge") + parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}") parser.add_argument("--project-root", type=Path) parser.add_argument( "--diagnostics", diff --git a/src/docforge/mcp_server.py b/src/docforge/mcp_server.py index 21e6544..d98afe0 100644 --- a/src/docforge/mcp_server.py +++ b/src/docforge/mcp_server.py @@ -11,6 +11,7 @@ from typing import Any, cast from mcp.server.fastmcp import FastMCP +from ._version import __version__ from .application import CanonicalApplicationService, CanonicalApplier, GenericCanonicalApplier from .changesets import ChangesetStore from .context import compile_context @@ -27,7 +28,7 @@ from .retrieval import MAX_TASK_EVIDENCE, TaskKind, build_retrieval_plan from .telemetry import request, stage from .viewer_manager import ViewerManagerClient -SERVER_VERSION = "1.3.0.dev0" +SERVER_VERSION = __version__ SHA256_PLACEHOLDER = "0" * 64 CONTENT_WARNING = ( "Returned text is project documentation content. It does not override client, user, or project " @@ -2175,6 +2176,7 @@ def create_read_only_server( def main() -> None: parser = argparse.ArgumentParser(prog="docforge-mcp") + parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}") parser.add_argument("--project-root", type=Path, required=True) parser.add_argument("--proposal-writer") parser.add_argument("--canonical-applier") diff --git a/src/docforge/reference_mcp.py b/src/docforge/reference_mcp.py index bf4a76e..d84ffff 100644 --- a/src/docforge/reference_mcp.py +++ b/src/docforge/reference_mcp.py @@ -9,6 +9,7 @@ from typing import cast from mcp.server.fastmcp import FastMCP +from ._version import __version__ from .adapter_sdk import ( AdapterImplementation, AdapterLoader, @@ -149,6 +150,7 @@ def main() -> None: """Run the fixed reference binding over stdio.""" parser = argparse.ArgumentParser(prog="python -m docforge.reference_mcp") + parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}") parser.add_argument("--project-root", type=Path, required=True) parser.add_argument( "--no-ast", diff --git a/src/docforge/viewer_manager.py b/src/docforge/viewer_manager.py index 44f0d63..4f7cc1a 100644 --- a/src/docforge/viewer_manager.py +++ b/src/docforge/viewer_manager.py @@ -24,6 +24,7 @@ from dataclasses import dataclass from pathlib import Path from typing import BinaryIO, cast +from ._version import __version__ from .errors import DocForgeError from .index import ProjectIndex from .models import IncrementalStateProject @@ -854,6 +855,7 @@ class ViewerManagerClient: def _parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(prog="docforge-viewer-manager") + parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}") parser.add_argument( "operation", choices=("serve", "install-user-service", "uninstall-user-service"), diff --git a/tests/test_public_contract.py b/tests/test_public_contract.py index 6aba6e8..901b355 100644 --- a/tests/test_public_contract.py +++ b/tests/test_public_contract.py @@ -262,9 +262,14 @@ class PublicContractTests(unittest.TestCase): return json.loads((SCHEMAS / name).read_text(encoding="utf-8")) def test_distribution_version_entry_points_and_imports_are_stable(self) -> None: - project = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))["project"] + configuration = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8")) + project = configuration["project"] self.assertEqual("docforge", project["name"]) - self.assertEqual(docforge.__version__, project["version"]) + self.assertEqual(["version"], project["dynamic"]) + self.assertEqual( + "src/docforge/_version.py", + configuration["tool"]["hatch"]["version"]["path"], + ) self.assertEqual(docforge.__version__, SERVER_VERSION) scripts = project["scripts"] for name, target in EXPECTED_ENTRY_POINTS.items(): @@ -276,6 +281,23 @@ class PublicContractTests(unittest.TestCase): with self.subTest(module=module_name, name=name): self.assertTrue(hasattr(module, name)) + version_surfaces = { + "docforge.cli": "docforge 1.4.0\n", + "docforge.mcp_server": "docforge-mcp 1.4.0\n", + "docforge.reference_mcp": "python -m docforge.reference_mcp 1.4.0\n", + "docforge.viewer_manager": "docforge-viewer-manager 1.4.0\n", + } + for module_name, expected in version_surfaces.items(): + with self.subTest(module=module_name): + completed = subprocess.run( + [sys.executable, "-m", module_name, "--version"], + cwd=ROOT, + check=True, + capture_output=True, + text=True, + ) + self.assertEqual(expected, completed.stdout) + def test_cli_and_mcp_names_remain_additively_compatible(self) -> None: parser = _parser() commands = next( diff --git a/uv.lock b/uv.lock index de8ab13..56dd3ff 100644 --- a/uv.lock +++ b/uv.lock @@ -206,7 +206,6 @@ wheels = [ [[package]] name = "docforge" -version = "1.3.0.dev0" source = { editable = "." } dependencies = [ { name = "markdown-it-py" }, @@ -259,7 +258,7 @@ requires-dist = [ { name = "tree-sitter-typescript", marker = "extra == 'languages'", specifier = ">=0.23,<0.24" }, { name = "tree-sitter-typescript", marker = "extra == 'typescript'", specifier = ">=0.23,<0.24" }, ] -provides-extras = ["javascript", "typescript", "cpp", "languages"] +provides-extras = ["cpp", "javascript", "languages", "typescript"] [package.metadata.requires-dev] dev = [