diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml
new file mode 100644
index 0000000..968d1f8
--- /dev/null
+++ b/.github/workflows/release-check.yml
@@ -0,0 +1,11 @@
+name: "🔖 Release PR check"
+
+on:
+ pull_request:
+ branches: [main]
+ types: [opened, synchronize, reopened, edited]
+
+jobs:
+ release-check:
+ name: "🤙 Call SDK release-check workflow"
+ uses: clamsproject/.github/.github/workflows/sdk-release-check.yml@main
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e8939f1..0e177ca 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -75,6 +75,13 @@ from clams.ver import __version__
For a dev install without a matching tag, `setuptools-scm` generates a
version like `1.4.1.dev20+gaf551a4e4.d20260325`.
+## Releases
+
+Release PRs (from `develop` to `main`) are gated by the `release-check` CI workflow. Before opening one:
+
+1. Title the PR exactly `releasing X.Y.Z` (strict semver); `release-check` reads the version from the title.
+2. Run `python build-tools/prep_release.py X.Y.Z` and commit its changes. See inside the prep script to see what's actually prepared.
+
## Migration from Makefile
The old `Makefile` and `setup.py` have been removed. If you are
diff --git a/build-tools/prep_release.py b/build-tools/prep_release.py
new file mode 100644
index 0000000..416107c
--- /dev/null
+++ b/build-tools/prep_release.py
@@ -0,0 +1,104 @@
+"""
+Prepare release artifacts for the clams-python package.
+
+Run while preparing a release PR, passing the version being released::
+
+ python build-tools/prep_release.py X.Y.Z
+
+Currently this adds a row to ``documentation/target-versions.csv`` for
+the release version. The ``mmif-python`` column is read from this
+package's pinned dependency in ``pyproject.toml``, and the target MMIF
+spec version from the installed ``mmif.__specver__``. This script is the
+single place that writes the target-versions table; the docs build only
+renders it, and CI verifies (on the release PR) that a row for the PR's
+version exists.
+
+Requires an editable install (``pip install -e .``) so that
+``mmif.__specver__`` is importable.
+"""
+import argparse
+import re
+import sys
+from pathlib import Path
+
+PROJECT_ROOT = Path(__file__).parent.parent
+CSV_PATH = PROJECT_ROOT / 'documentation' / 'target-versions.csv'
+PYPROJECT_PATH = PROJECT_ROOT / 'pyproject.toml'
+
+VERSION_RE = re.compile(r'^\d+\.\d+\.\d+$')
+TOP_ROW_RE = re.compile(r'`(\d+\.\d+\.\d+) <')
+MMIF_PIN_RE = re.compile(r'mmif-python\s*==\s*(\d+\.\d+\.\d+)')
+
+
+def _row(version, mmif_ver, specver):
+ return (
+ f'`{version} `__,'
+ f'`{mmif_ver} `__,'
+ f'`{specver} `__'
+ )
+
+
+def _version_tuple(v):
+ return tuple(int(x) for x in v.split('.'))
+
+
+def _top_version(data_lines):
+ if not data_lines:
+ return None
+ m = TOP_ROW_RE.match(data_lines[0])
+ return m.group(1) if m else None
+
+
+def _pinned_mmif_version():
+ m = MMIF_PIN_RE.search(PYPROJECT_PATH.read_text())
+ if not m:
+ sys.exit("Error: could not find a pinned 'mmif-python==X.Y.Z' "
+ "dependency in pyproject.toml.")
+ return m.group(1)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Prepare release artifacts for clams-python."
+ )
+ parser.add_argument(
+ "version", metavar="X.Y.Z",
+ help="the release version being prepared",
+ )
+ args = parser.parse_args()
+ version = args.version
+
+ if not VERSION_RE.match(version):
+ sys.exit(f"Error: '{version}' is not a valid X.Y.Z version.")
+
+ mmif_ver = _pinned_mmif_version()
+ try:
+ import mmif
+ specver = mmif.__specver__
+ except (ImportError, AttributeError):
+ sys.exit("Error: cannot read mmif.__specver__. "
+ "Run `pip install -e .` first.")
+
+ lines = CSV_PATH.read_text().splitlines()
+ header, data = lines[0], lines[1:]
+ top = _top_version(data)
+
+ if top is not None and _version_tuple(version) < _version_tuple(top):
+ sys.exit(f"Error: {version} is older than the current top row "
+ f"{top}; refusing to insert an out-of-order version.")
+
+ new_row = _row(version, mmif_ver, specver)
+ if top == version:
+ data[0] = new_row # update in place
+ action = "updated"
+ else:
+ data.insert(0, new_row) # prepend as the new latest
+ action = "added"
+
+ CSV_PATH.write_text('\n'.join([header] + data) + '\n')
+ print(f"target-versions.csv: {action} row {version} "
+ f"-> mmif-python {mmif_ver}, MMIF spec {specver}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/documentation/conf.py b/documentation/conf.py
index e056cd2..437c394 100644
--- a/documentation/conf.py
+++ b/documentation/conf.py
@@ -14,8 +14,6 @@
import inspect
import json
import os
-import re
-import shutil
import subprocess
import sys
from pathlib import Path
@@ -196,24 +194,6 @@ def generate_jsonschema(app):
json.dump(schema_dict, f, indent=2)
-def update_target_spec(app):
- target_vers_csv = Path(__file__).parent / 'target-versions.csv'
- version = _get_version('clams-python')
- # Skip dev/dummy versions to avoid dirtying the git-tracked CSV
- if 'dev' in version or not re.match(r'^\d+\.\d+\.\d+$', version):
- return
- mmifver = mmif.__version__
- specver = mmif.__specver__
- with open(target_vers_csv) as in_f, open(f'{target_vers_csv}.new', 'w') as out_f:
- lines = in_f.readlines()
- if not lines[1].startswith(f"`{version}"):
- lines.insert(1, f"`{version} `__,`{mmifver} `__,`{specver} `__\n")
- for line in lines:
- out_f.write(line)
- shutil.move(out_f.name, in_f.name)
-
-
def setup(app):
app.connect('builder-inited', generate_whatsnew_rst)
app.connect('builder-inited', generate_jsonschema)
- app.connect('builder-inited', update_target_spec)
diff --git a/documentation/target-versions.csv b/documentation/target-versions.csv
index 5ae092a..8436941 100644
--- a/documentation/target-versions.csv
+++ b/documentation/target-versions.csv
@@ -1,4 +1,9 @@
"``clams-python`` version","``mmif-python`` version","Target MMIF Specification"
+`1.7.1 `__,`1.5.1 `__,`1.2.0 `__
+`1.7.0 `__,`1.5.0 `__,`1.2.0 `__
+`1.6.0 `__,`1.4.0 `__,`1.1.1 `__
+`1.5.0 `__,`1.3.1 `__,`1.1.0 `__
+`1.4.0 `__,`1.2.1 `__,`1.1.0 `__
`1.3.3 `__,`1.1.2 `__,`1.1.0 `__
`1.3.2 `__,`1.1.1 `__,`1.1.0 `__
`1.3.1 `__,`1.0.19 `__,`1.0.5 `__
@@ -50,4 +55,4 @@
`0.1.7 `__,`0.2.2 `__,`0.2.1 `__
`0.1.6 `__,`0.2.1 `__,`0.2.1 `__
`0.1.5 `__,`0.2.1 `__,`0.2.1 `__
-`0.1.4 `__,`0.2.1 `__,`0.2.1 `__
\ No newline at end of file
+`0.1.4 `__,`0.2.1 `__,`0.2.1 `__
diff --git a/pyproject.toml b/pyproject.toml
index 048410d..b7f9fc1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -19,7 +19,7 @@ classifiers = [
"Programming Language :: Python :: 3 :: Only",
]
dependencies = [
- "mmif-python==1.5.0",
+ "mmif-python==1.5.1",
"Flask>=2",
"Flask-RESTful>=0.3.9",
"gunicorn>=20",