Capability: release-publishing
Automatic, semantic releases: merging to
mainversions, tags and publishes the CLI to PyPI with no stored credentials.
What it is
The release pipeline for the CLI package — versioning derived from Conventional Commits, publishing via GitHub Actions Trusted Publishing (OIDC).
Current behaviour
- WHEN commits land on
mainTHEN.github/workflows/release.ymlSHALL derive the next version withcz bump(commitizen) from the Conventional Commits / PR titles since the last tag, tag it, and push the tag explicitly. - The workflow SHALL build the package with
uvand publish to PyPI using Trusted Publishing (OIDC) — no stored API token — gated by thepypiGitHub environment. - The distribution SHALL be named
the-loopy-one(base name taken) while the import packagethe_loopand thethe-loopconsole script stay unchanged. - WHEN no release-worthy commit exists THEN the workflow SHALL exit without publishing (first-release baseline handled via a local-only tag).
- WHEN a bump happens THEN the Claude Code and Cursor plugin manifests (
.claude-plugin/+.cursor-plugin/plugin.json/marketplace.json) SHALL be rewritten to the new version in the same bump commit (.cz.tomlversion_files), and a PR-time lockstep check (cli/tests/test_version_lockstep.py) SHALL fail any change that lets a versioned artifact drift from the commitizen version. - WHEN a bump happens THEN
uv.lockSHALL be re-resolved and folded into the same bump commit, with the release tag moved onto the amended commit — the lockfile records the workspace member's own version, andcz bumpcannot rewrite it (every one of its ~70version = "…"lines would match aversion_filespattern). - WHEN a pull request is checked THEN CI SHALL sync with
uv sync --locked, so a lockfile that has fallen behind fails the run instead of being rewritten inside the runner and thrown away. - WHEN a release run starts THEN it SHALL check out
main's tip rather than its event SHA, so a queued run builds on the bump commit the run before it pushed. A run whose commits are already released is a no-op. - WHEN the bump commit and its tag are pushed THEN they SHALL be pushed atomically. If
mainmoved during the run, neither ref lands. A tagmaindoes not carry would make every later run fail with "tag already exists". The run queued for the newer merge then releases everything. A PR-time test (cli/tests/test_release_workflow.py) holds both properties. - The uv version SHALL be pinned in the workflows and bounded on developer machines (
[tool.uv] required-versionin the rootpyproject.toml), because the uv that writes the committed lockfile decides its text — RULE: no local-vs-CI drift.
Design
docs/specs/issue-21/design.md · docs/specs/issue-407/design.md · docs/specs/issue-460/bugfix.md · architecture § CLI companion
History
| Work item | What changed | Links |
|---|---|---|
| issue-460 | Release builds on main's tip and pushes the bump commit + tag atomically. Recovered from the orphan v19.22.0 tag by moving the repository to 19.22.0. That version was tagged but never published, so the next release is 19.22.1 | spec |
| issue-407 | uv.lock re-locked into the bump commit, uv sync --locked in CI, uv pinned, and the lockstep guard extended to the lockfile | spec |
| issue-46 | Plugin + marketplace manifests versioned in lockstep with releases (commitizen version_files), with a PR-time drift guard | spec, decision-028 |
| issue-21 | Introduced PyPI Trusted Publishing + automatic semantic releases (incl. tag-push and first-release fixes) | spec, decision-019 |