Skip to content

chore: add end-to-end version-bump script#434

Open
saturley-hall wants to merge 1 commit into
mainfrom
harrison/version-bump-script
Open

chore: add end-to-end version-bump script#434
saturley-hall wants to merge 1 commit into
mainfrom
harrison/version-bump-script

Conversation

@saturley-hall

@saturley-hall saturley-hall commented Jun 11, 2026

Copy link
Copy Markdown
Member

Summary

Adds scripts/bump_version.py, a stdlib-only tool that automates the on-main
"Bumping the ModelExpress Version" procedure documented in CLAUDE.md. The repo
is already at 0.5.0, so this captures the procedure as a reusable, parameterized
tool rather than a one-shot edit.

It runs a five-stage pipeline, each skippable, with --dry-run:

  1. literals - workspace/chart/Python version strings plus every mx_version
    fixture/example, discovered across the tree.
  2. Cargo.lock - cargo update --workspace.
  3. uv.lock - uv lock.
  4. hashes - mx_version feeds the SHA256-derived mx_source_id, so the pinned
    hashes change. Runs the pinned tests, parses the new hashes, patches both the
    Python assertions and the Rust cross-checks, then re-runs to confirm. A bad
    regeneration leaves the tests red and aborts.
  5. verify - cargo check --workspace --tests, the Rust source_identity
    tests, and the Python test suite.
python scripts/bump_version.py 0.6.0 --dry-run   # preview
python scripts/bump_version.py 0.6.0             # full run

Closes two gaps in the documented manual procedure

  • An mx_version fixture in workspace-tests/tests/artifact_transfer_contract.rs
    that the documented file list omits (the discovery pass finds it).
  • Pinned-hash occurrences the documented -k "pinned_hash or case_colliding"
    filter misses (test_empty_artifact_fields_preserve_existing_id and its Rust
    counterpart). The global old->new hash replacement covers all of them.

Build isolation

scripts/ is added to .dockerignore (the Dockerfile does COPY . .) and lives
outside the Python wheel (built from modelexpress_client/python/), so the tool
never ships to users.

Does NOT touch

  • Public release image tags (nvcr.io/.../modelexpress-server:<tag>) - separate
    release-branch cadence.
  • External crate versions in Cargo.lock.
  • docs/CLI.md example outputs.

Docs

Usage note in CLAUDE.md (synced to .github/copilot-instructions.md and
.cursor/rules/rust.mdc) and a CONTRIBUTING.md command-table entry.

Verification

  • Dry-run reports exactly 11 files / 17 replacements; nothing in the guard list.
  • Real 0.5.0 -> 0.6.0 -> 0.5.0 round-trip restores source files byte-for-byte; the
    only Cargo.lock diff is the four ModelExpress crates.
  • Rust source_identity tests pass with the Python-derived hashes (cross-language
    parity), 17/17.
  • Full end-to-end run exercised all five stages.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Added automated version bumping script for coordinated workspace version updates across configuration files and lock files
    • Expanded developer documentation with version management workflow and available build commands
    • Optimized Docker build context to exclude internal scripts directory

Add scripts/bump_version.py, a stdlib-only tool that automates the
on-main "Bumping the ModelExpress Version" procedure: it edits the
workspace/chart/Python version literals and every mx_version fixture,
regenerates Cargo.lock and uv.lock, regenerates the SHA256-derived
mx_source_id pinned-hash assertions in both Python and Rust, and
verifies with cargo and pytest. Supports --dry-run and per-stage
--skip-* flags.

The discovery-based literal pass and global hash replacement close two
gaps in the documented manual procedure: an mx_version fixture in
workspace-tests/ that the file list omits, and pinned-hash occurrences
the -k test filter misses.

scripts/ is excluded from the Docker build context via .dockerignore
and lives outside the Python wheel, so the tool never ships to users.

Document usage in CLAUDE.md (synced to .github/copilot-instructions.md
and .cursor/rules/rust.mdc) and add a CONTRIBUTING.md command entry.

Signed-off-by: Harrison King Saturley-Hall <hsaturleyhal@nvidia.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR introduces scripts/bump_version.py, a comprehensive CLI tool that automates workspace version bumping across Cargo.toml, pyproject.toml, helm/Chart.yaml, and source files. The tool includes optional lock file regeneration, pinned hash updates via test-driven discovery, and end-to-end verification. Supporting documentation and build tooling integration have been added throughout the repository.

Changes

Version Bumping Automation

Layer / File(s) Summary
Script Documentation
scripts/README.md
Complete guide covering usage, prerequisites, five-stage control flow (version literals, Cargo.lock, uv.lock, hash regeneration, verification), explicit non-touched areas (public images, external crates, static docs), and validation instructions.
Core Setup & Utilities
scripts/bump_version.py (lines 1–222)
Repo path constants, file scanning configuration, version/hash regex patterns, TOML-scoped replacement helpers, dry-run-aware file editing, version detection and semver validation, subprocess wrapper, and environment/tool availability checks.
Stage 1: Version Literal Updates
scripts/bump_version.py (lines 111–180)
TOML version replacement in Cargo.toml (workspace + path deps), pyproject.toml, and helm/Chart.yaml; repository-wide file discovery with VCS/cache pruning; mx_version literal scanning and replacement.
Stage 4: Source-ID Hash Regeneration
scripts/bump_version.py (lines 229–279)
Test-driven hash extraction by running pytest tests/test_source_id.py, parsing new vs pinned 16-hex pairs from assertion output, patching both Python and Rust source literals, and rerunning to confirm regeneration success.
Stage 5: Build & Test Verification
scripts/bump_version.py (lines 286–301)
Runs cargo check --workspace --tests, targeted p2p::source_identity Rust test, and pytest tests/; fails the script if any verification command returns non-zero outside dry-run.
CLI Interface & Orchestration
scripts/bump_version.py (lines 307–388)
Argument parsing (version, --dry-run, --skip-locks/--skip-hashes/--skip-verify, --repo-root); repo-root resolution; end-to-end stage orchestration with conditional execution; matched literal reporting; exit code handling.
Build System Integration
.dockerignore, .cursor/rules/rust.mdc, .github/copilot-instructions.md, CLAUDE.md, CONTRIBUTING.md
Scripts/ excluded from Docker context; bump_version.py command documented in build/test checklists and contributing guides; CLAUDE.md clarifies automation with fallback manual instructions; CONTRIBUTING.md adds command to Available Commands list.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The changes introduce a substantial new Python CLI tool (388 lines of logic) with five operational stages, requiring careful review of version validation, file replacement mechanics, test-driven hash extraction, and subprocess handling. However, the implementation is well-structured by stage, uses familiar patterns, and supporting documentation is comprehensive. Configuration and doc updates are straightforward. The moderate complexity stems from the multi-stage orchestration and regex-based file patching rather than exotic patterns.

Poem

🐰 A rabbit hops through versions bright,
Bumping semvers left and right!
Hashes patched, locks regenerate,
Automation keeps things straight.
No more manual scripts to write—
Version bumps are done just right! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: add end-to-end version-bump script' directly and clearly summarizes the main change: adding a new version-bump script that automates the end-to-end procedure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CLAUDE.md (1)

23-36: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing bump_version.py command in Build and Test Commands section.

The "Build and Test Commands" section in CLAUDE.md does not include the python scripts/bump_version.py 0.6.0 command, but both .cursor/rules/rust.mdc (line 41) and .github/copilot-instructions.md (line 41) include it. Per coding guidelines, these three files should be synchronized when making build command changes.

📝 Proposed fix to add the command
 ./run_integration_tests.sh           # Integration tests (starts server)
+python scripts/bump_version.py 0.6.0  # Bump workspace/chart/python version + source-id hashes (see scripts/README.md)
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @CLAUDE.md around lines 23 - 36, The Build and Test Commands section in
CLAUDE.md is missing the version bump step; add the missing command "python
scripts/bump_version.py 0.6.0" into the listed commands so CLAUDE.md matches
.cursor/rules/rust.mdc and .github/copilot-instructions.md; update the commands
block under "## Build and Test Commands" to include that exact bump_version
invocation as a separate line consistent with the other entries.


</details>

<!-- cr-comment:v1:dac03369b9c8de8ef3a28b4b -->

_Source: Coding guidelines_

</blockquote></details>

</blockquote></details>
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/bump_version.py`:
- Around line 365-369: The script currently calls
require_tool("cargo")/run(["cargo", "update", "--workspace"], repo_root,
args.dry_run) and require_tool("uv")/run(["uv", "lock"], repo_root / PYTHON_DIR,
args.dry_run) but ignores failures; change the calls so that run's non-zero exit
causes immediate failure: either have run raise an exception on non-zero or
capture its return value and call sys.exit(1) (or raise RuntimeError) when the
command fails for both the cargo update and uv lock invocations, ensuring the
script stops rather than printing success after a failed lock stage.
- Line 60: The function signature def fail(message: str) -> "NoReturn": uses
NoReturn as a quoted name and suppresses the lint error; import NoReturn from
typing (add from typing import NoReturn) and remove the string literal/type:
ignore so the signature becomes def fail(message: str) -> NoReturn:. Also
replace the lambda assigned to sub (currently sub = lambda ...) with a properly
named def (e.g., def sub(...): return ...) to satisfy E731 and keep the same
behavior; update any references to sub unchanged.

---

Outside diff comments:
In `@CLAUDE.md`:
- Around line 23-36: The Build and Test Commands section in CLAUDE.md is missing
the version bump step; add the missing command "python scripts/bump_version.py
0.6.0" into the listed commands so CLAUDE.md matches .cursor/rules/rust.mdc and
.github/copilot-instructions.md; update the commands block under "## Build and
Test Commands" to include that exact bump_version invocation as a separate line
consistent with the other entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4738a9da-fcfa-4892-8a89-319a024ccc5f

📥 Commits

Reviewing files that changed from the base of the PR and between e6987ce and 48c8279.

📒 Files selected for processing (7)
  • .cursor/rules/rust.mdc
  • .dockerignore
  • .github/copilot-instructions.md
  • CLAUDE.md
  • CONTRIBUTING.md
  • scripts/README.md
  • scripts/bump_version.py

Comment thread scripts/bump_version.py
HEX16 = r"[0-9a-f]{16}"


def fail(message: str) -> "NoReturn": # type: ignore[name-defined]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ruff check scripts/bump_version.py --select F821,E731

Repository: ai-dynamo/modelexpress

Length of output: 1196


Fix lint blockers on NoReturn and lambda assignment.
ruff check scripts/bump_version.py --select F821,E731 fails with:

  • F821 at line 60: NoReturn is used but never imported (def fail(...) -> "NoReturn": # type: ignore[name-defined])
  • E731 at line 114: sub = lambda ... triggers “Do not assign a lambda expression, use a def
Proposed fix
 from pathlib import Path
+from typing import NoReturn
@@
-def fail(message: str) -> "NoReturn":  # type: ignore[name-defined]
+def fail(message: str) -> NoReturn:
@@
-    sub = lambda m: m.group(1) + new + m.group(2)
+    def sub(m):
+        return m.group(1) + new + m.group(2)
🧰 Tools
🪛 Ruff (0.15.15)

[error] 60-60: Undefined name NoReturn

(F821)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/bump_version.py` at line 60, The function signature def fail(message:
str) -> "NoReturn": uses NoReturn as a quoted name and suppresses the lint
error; import NoReturn from typing (add from typing import NoReturn) and remove
the string literal/type: ignore so the signature becomes def fail(message: str)
-> NoReturn:. Also replace the lambda assigned to sub (currently sub = lambda
...) with a properly named def (e.g., def sub(...): return ...) to satisfy E731
and keep the same behavior; update any references to sub unchanged.

Source: Linters/SAST tools

Comment thread scripts/bump_version.py
Comment on lines +365 to +369
require_tool("cargo")
run(["cargo", "update", "--workspace"], repo_root, args.dry_run)
print("[3/5] uv.lock")
require_tool("uv")
run(["uv", "lock"], repo_root / PYTHON_DIR, args.dry_run)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle lock regeneration command failures immediately.

Line 365-369 runs cargo update and uv lock but ignores exit codes, so the script can continue and print success after a failed lock stage.

Proposed fix
     else:
         print("[2/5] cargo.lock")
         require_tool("cargo")
-        run(["cargo", "update", "--workspace"], repo_root, args.dry_run)
+        cargo_res = run(["cargo", "update", "--workspace"], repo_root, args.dry_run)
+        if not args.dry_run and cargo_res.returncode != 0:
+            fail("lock regeneration failed: cargo update --workspace")
         print("[3/5] uv.lock")
         require_tool("uv")
-        run(["uv", "lock"], repo_root / PYTHON_DIR, args.dry_run)
+        uv_res = run(["uv", "lock"], repo_root / PYTHON_DIR, args.dry_run)
+        if not args.dry_run and uv_res.returncode != 0:
+            fail("lock regeneration failed: uv lock")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
require_tool("cargo")
run(["cargo", "update", "--workspace"], repo_root, args.dry_run)
print("[3/5] uv.lock")
require_tool("uv")
run(["uv", "lock"], repo_root / PYTHON_DIR, args.dry_run)
require_tool("cargo")
cargo_res = run(["cargo", "update", "--workspace"], repo_root, args.dry_run)
if not args.dry_run and cargo_res.returncode != 0:
fail("lock regeneration failed: cargo update --workspace")
print("[3/5] uv.lock")
require_tool("uv")
uv_res = run(["uv", "lock"], repo_root / PYTHON_DIR, args.dry_run)
if not args.dry_run and uv_res.returncode != 0:
fail("lock regeneration failed: uv lock")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/bump_version.py` around lines 365 - 369, The script currently calls
require_tool("cargo")/run(["cargo", "update", "--workspace"], repo_root,
args.dry_run) and require_tool("uv")/run(["uv", "lock"], repo_root / PYTHON_DIR,
args.dry_run) but ignores failures; change the calls so that run's non-zero exit
causes immediate failure: either have run raise an exception on non-zero or
capture its return value and call sys.exit(1) (or raise RuntimeError) when the
command fails for both the cargo update and uv lock invocations, ensuring the
script stops rather than printing success after a failed lock stage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant