Skip to content

feat(release): release.py + version lockstep SSOT (P1) - #40

Merged
elkaix merged 9 commits into
mainfrom
p1/release-tool
Jun 1, 2026
Merged

feat(release): release.py + version lockstep SSOT (P1)#40
elkaix merged 9 commits into
mainfrom
p1/release-tool

Conversation

@elkaix

@elkaix elkaix commented Jun 1, 2026

Copy link
Copy Markdown
Member

Related Issue

  • Part of release orchestration P1 plan: docs/superpowers/plans/2026-05-31-release-orchestration-p1-release-tool.md.

Description

  • Adds scripts/release.py, a four-phase release-prep tool that validates release preconditions, rewrites version-derived files from pyproject.toml, runs local gates, and opens a release/X.Y.Z PR.
  • Adds tests/test_version_lockstep.py so version-bearing strings and workspace pins stay aligned on every PR, including the frozen pythinker-review==0.1.0 pin.
  • Extends dependency-version checks and both CI/release callers to include pythinker-review with pinned uv 0.8.5 setup where uv run is used.
  • Adds PYTHINKER_MANAGED updater handling with channel-native guidance, cache persistence for managed check-only update checks, and Homebrew regression coverage.
  • Documents release-skill usage and AGENTS.md root-cause/validation guidance.

Checklist

  • Implemented release-tool and version-lockstep tests.
  • Extended dependency checks and workflow callers atomically.
  • Added managed-channel updater handling and regression tests.
  • Added/kept root CHANGELOG.md entry; left generated docs changelog manual sync alone.
  • Ran focused local pytest/ruff/pyright/workflow-parse checks.
  • Addressed CodeRabbit actionable comments from the first review pass.

Summary by CodeRabbit

Release Notes

  • New Features
    • Managed-channel installations now display channel-specific update guidance instead of generic installation commands.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e6d3c820-3e64-4e94-ac45-1ab146dfae65

📥 Commits

Reviewing files that changed from the base of the PR and between be56b79 and 5539218.

📒 Files selected for processing (6)
  • .github/workflows/ci-pythinker-cli.yml
  • .github/workflows/release-pythinker-cli.yml
  • scripts/release.py
  • src/pythinker_code/ui/shell/update.py
  • tests/test_release_py.py
  • tests/ui_and_conv/test_shell_update.py

📝 Walkthrough

Walkthrough

This PR establishes a single source of truth for versioning by introducing scripts/release.py, a multi-phase release orchestrator that validates preconditions, rewrites version strings across TOML and documentation, executes local CI gates, and opens release PRs without tagging. It adds comprehensive version lockstep contract tests, expands dependency validation to pythinker-review, and implements managed-channel update support for containerized installations.

Changes

Release Infrastructure and Orchestration

Layer / File(s) Summary
Release script foundations and TOML operations
scripts/release.py (lines 1–114)
Semver parsing and monotonicity validation; TOML read/write for project versions; dependency pin rewriting with extras marker preservation; ReleaseError exception class.
Changelog promotion and version string rewriting
scripts/release.py (lines 116–157)
Replaces ## Unreleased with dated ## X.Y.Z (DATE) while preserving body; reinjects empty Unreleased; rewrites version strings in release patterns (headers, installers, artifacts, URLs) while skipping documented exceptions.
Subprocess helpers and orchestration phases
scripts/release.py (lines 159–304)
Phase 1: validates target semver, enforces clean git state, confirms main alignment with origin/main, checks changelog anchors. Phase 2: updates root/subpackage versions, rewrites pins and changelogs, rewrites markdown strings, runs uv lock. Phase 3: executes local gates (version checks, dependency validation, tests). Phase 4: creates release branch, commits, pushes, opens PR via gh, prints maintainer tag instructions.
CLI and main control flow
scripts/release.py (lines 306–345)
Argument parsing for --set-version, optional --bump-core/--bump-host/--dry-run; orchestrates phases with exception handling mapping failures to exit code 1; error formatter for subprocess output.
Release orchestrator unit tests
tests/test_release_py.py
Tests semver parsing (X.Y.Z shape only), monotonicity (increases allowed, equal/lower rejected), root version round-tripping, dependency pin updates preserving extras, changelog promotion with dated headers and empty Unreleased reinsertion, error cases (missing deps, missing anchors), version string rewriting across multiple patterns, and dry-run PR output.
Version lockstep contract tests
tests/test_version_lockstep.py
Enforces root version matches MAJOR.MINOR.PATCH; subpackage pins match project versions; pythinker-review frozen at 0.1.0; README contains "What's New" heading and pip snippet; CHANGELOG has dated heading; asset names (executables, packages, URLs, README patterns) match version across docs; no hardcoded version in badge URLs; --version examples use valid semver shape.

Managed Channel Update Support

Layer / File(s) Summary
Managed channel detection and flow routing
src/pythinker_code/ui/shell/update.py (lines 64–1239)
MANAGED_CHANNEL_MARKER constant tags managed installs. _detect_upgrade_command() returns [marker, channel] when PYTHINKER_MANAGED env var set. _update_prompt_text() renders channel-specific message. _do_update() short-circuits with channel instruction and returns UPDATE_AVAILABLE, bypassing readiness checks.
Managed channel behavior tests
tests/ui_and_conv/test_shell_update.py (lines 532–1264)
Tests _detect_upgrade_command() uses Homebrew when unset, returns marker+channel when PYTHINKER_MANAGED set. _update_prompt_text() renders channel name without raw marker. do_update() caches version and prints channel instruction in check_only mode.

Workflow Infrastructure and Dependency Validation

Layer / File(s) Summary
Dependency validation expansion
scripts/check_pythinker_dependency_versions.py
Adds --pythinker-review-pyproject CLI argument; includes pythinker-review in pin validation loop alongside core and host packages.
GitHub Actions uv integration
.github/workflows/ci-pythinker-cli.yml, .github/workflows/release-pythinker-cli.yml
Both workflows add uv setup steps and run dependency-version checks via uv run python instead of direct python, with pythinker-review pyproject argument.
Release process documentation and validation
.agents/skills/release/SKILL.md, AGENTS.md, docs/en/release-notes/breaking-changes.md, CHANGELOG.md, tests/test_release_update_pipeline.py
SKILL.md documents uv run release.py command and notes uv lock/uv sync already included. Agent guidance adds root-cause investigation and authoritative-source validation rules. Breaking-changes and CHANGELOG introduce Unreleased sections. Changelog-required workflow test validates release-prep PRs skip changelog checks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The PR introduces substantial new orchestration logic (345 lines in release.py) with intricate multi-phase workflows combining version mutation, file rewriting, subprocess gating, and git/gh operations. The version lockstep test suite adds pervasive contract coverage across repository assets. Managed-channel implementation is narrowly scoped but requires tracing control flow through update paths. Heterogeneous changes span script logic, workflow wiring, test coverage, and documentation, each demanding separate reasoning.

Suggested labels

enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.39% which is insufficient. The required threshold is 70.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commits format with valid type 'feat', scope 'release', and a clear description of the main changes.
Description check ✅ Passed The description covers related issues, detailed changes, and includes a completed checklist matching the template structure.
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.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch p1/release-tool

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: 4

Caution

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

⚠️ Outside diff range comments (1)
tests/ui_and_conv/test_shell_update.py (1)

1223-1238: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add a regression test for managed do_update(check_only=True) cache persistence.

These tests cover detection and prompt rendering, but not the managed /update check-only path that must persist LATEST_VERSION_FILE for follow-up flows.

🤖 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 `@tests/ui_and_conv/test_shell_update.py` around lines 1223 - 1238, Add a
regression test that verifies do_update(check_only=True) persists the
managed-channel latest-version cache: in the existing test module set
PYTHINKER_MANAGED via monkeypatch and sys.executable to simulate managed mode,
call update.do_update(check_only=True) (or the async equivalent), then assert
that the file referenced by update.LATEST_VERSION_FILE exists and contains the
fetched latest version (or at least a non-empty value) so follow-up flows can
read it; reference the functions/values _detect_upgrade_command, do_update, and
LATEST_VERSION_FILE to find the right code paths to exercise and validate.
🤖 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 @.github/workflows/ci-pythinker-cli.yml:
- Around line 253-257: The release-validate job calls "uv run python
scripts/check_pythinker_dependency_versions.py" but never installs the uv CLI;
add a step in the release-validate job immediately before the "uv run"
invocation that installs the pinned uv toolchain (e.g. run "python -m pip
install 'uv==0.8.5'" or equivalent using the repository's preferred installer)
so the workflow does not rely on runner state and consistently uses uv 0.8.5.

In `@scripts/release.py`:
- Around line 179-183: The code currently validates main refs but later creates
the release branch from the current HEAD, which can include unrelated commits;
change the branch creation to start from origin/main instead of HEAD by using
the remote ref you already capture (remote = _git_capture(["git", "rev-parse",
"origin/main"])) as the base when making the release branch (e.g., create
release/{target} from the commit hash stored in remote via git checkout -b or
git branch <name> <remote>), and keep the existing checks (local, remote,
read_project_version, assert_monotonic) intact.
- Around line 327-329: The except block in main() only handles ReleaseError so
subprocess.CalledProcessError from _git_capture()/_run() escapes; update the
exception handling to also catch subprocess.CalledProcessError (and/or Exception
if you prefer broader safety) and handle it the same way as ReleaseError:
capture the exception (e.g., exc), print a deterministic error message to
sys.stderr including relevant exception details, and return 1 so subprocess
failures produce controlled release-tool output; reference main(),
_git_capture(), _run(), ReleaseError, and subprocess.CalledProcessError when
locating the code to modify.

In `@src/pythinker_code/ui/shell/update.py`:
- Around line 1228-1235: The managed-channel branch in the upgrade flow (the
block checking upgrade_command[:1] == [MANAGED_CHANNEL_MARKER]) returns
UpdateResult.UPDATE_AVAILABLE before persisting latest_version to the cache,
which later causes run_update_prompt() to read stale data; modify that branch in
update.py so that before calling _print() and returning
UpdateResult.UPDATE_AVAILABLE you save/persist latest_version to the same
cache/store the normal update flow uses (the same mechanism other branches use
to write latest_version), ensuring functions like run_update_prompt() will see
the updated version even for managed-channel updates.

---

Outside diff comments:
In `@tests/ui_and_conv/test_shell_update.py`:
- Around line 1223-1238: Add a regression test that verifies
do_update(check_only=True) persists the managed-channel latest-version cache: in
the existing test module set PYTHINKER_MANAGED via monkeypatch and
sys.executable to simulate managed mode, call update.do_update(check_only=True)
(or the async equivalent), then assert that the file referenced by
update.LATEST_VERSION_FILE exists and contains the fetched latest version (or at
least a non-empty value) so follow-up flows can read it; reference the
functions/values _detect_upgrade_command, do_update, and LATEST_VERSION_FILE to
find the right code paths to exercise and validate.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 62f821eb-2d21-4805-b9bc-7f40b8c12c7e

📥 Commits

Reviewing files that changed from the base of the PR and between fb7fdbf and 4910cf2.

📒 Files selected for processing (12)
  • .agents/skills/release/SKILL.md
  • .github/workflows/ci-pythinker-cli.yml
  • .github/workflows/release-pythinker-cli.yml
  • AGENTS.md
  • docs/en/release-notes/breaking-changes.md
  • scripts/check_pythinker_dependency_versions.py
  • scripts/release.py
  • src/pythinker_code/ui/shell/update.py
  • tests/test_release_py.py
  • tests/test_release_update_pipeline.py
  • tests/test_version_lockstep.py
  • tests/ui_and_conv/test_shell_update.py

Comment thread .github/workflows/ci-pythinker-cli.yml
Comment thread scripts/release.py
Comment thread scripts/release.py
Comment thread src/pythinker_code/ui/shell/update.py

@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: 1

🤖 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 `@docs/en/release-notes/changelog.md`:
- Line 20: The line added to docs/en/release-notes/changelog.md is a manual edit
and must be removed so the file remains auto-synced from the root CHANGELOG.md;
delete the manually inserted sentence about "Release preparation now uses a
version single source of truth" from docs/en/release-notes/changelog.md and rely
on the sync script (the changelog sync that copies root CHANGELOG.md into
docs/en/release-notes/changelog.md) to propagate any official changelog entries
instead of editing this file directly.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: a25b78d4-ad6b-4ac7-a1fe-10e4c82d1ebf

📥 Commits

Reviewing files that changed from the base of the PR and between 4910cf2 and be56b79.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • docs/en/release-notes/changelog.md

Comment thread docs/en/release-notes/changelog.md Outdated
@elkaix
elkaix merged commit c1a90de into main Jun 1, 2026
32 checks passed
@elkaix
elkaix deleted the p1/release-tool branch July 17, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant