Decision 028: Version plugin manifests in lockstep via commitizen version_files (no second release tool)
- Status: accepted
- Date: 2026-07-23
- Deciders: @MadaraUchiha-314 (issue #46)
- Work item: issue-46
- Spec:
docs/specs/issue-46/
Context
Issue #46 asks for "semantic-release based updates to the-loop", modelled on alter-ego#8 (automated plugin versioning from conventional commits) and explicitly constrained to Python tools, not the TypeScript semantic-release.
the-loop already runs a Python semantic-release pipeline (decision-019): commitizen derives the version from Conventional Commits on merge to main, tags, generates the changelog, cuts a GitHub Release and publishes to PyPI. But the pipeline only versioned the CLI package: the Claude Code and Cursor plugin manifests (.claude-plugin/plugin.json, .claude-plugin/marketplace.json, .cursor-plugin/plugin.json, .cursor-plugin/marketplace.json) sat frozen at 0.1.0 while releases reached v0.7.0 — the exact manual-version problem alter-ego#8 automates away. Nothing detected the drift; commitizen's file rewriting is a string replacement of the current version, so a drifted file is silently skipped forever.
Decision
Extend the existing commitizen configuration rather than adopt any new tool.
- The four plugin manifests join
.cz.toml'sversion_files(pattern"version", which in the marketplace manifests covers both the metadata version and the plugin entry version), and are synced from0.1.0to the release-current version so replacement engages. Every futurecz bumprewrites CLI package, plugin manifests, changelog and tag in one commit. - A PR-time lockstep guard makes silent drift structurally impossible:
scripts/check_version_lockstep.py(stdlib-only) re-readsversion_filesfrom.cz.tomland requires every matching version line in every target to carry the current version. It runs ascli/tests/test_version_lockstep.pyinside the CLI test suite, so the existing pytest pre-commit hook and CI enforce it with no pipeline changes —release.ymland.pre-commit-config.yamlare untouched.
Consequences
- Installed-plugin and marketplace versions now tell the truth; the upgrade flow (
/the-loop:upgrade-the-loop, alter-ego#8's "integration with upgrades" point) has a real version to compare against. - Adding a future versioned artifact is one
version_filesline; the guard covers it automatically because it is driven by the same list. - The bump commit grows to eight rewritten version fields across six files — all mechanical, all in one
bump:commit. - Versions
0.1.0-era manifests shipped in older installs simply catch up on the next release; no migration is needed. - Known gap:
uv.lockalso records the package version (it lagged one release behind onmain; caught up here) but stays OUT ofversion_files— itsversionlines belong to every locked package, and commitizen's per-line string replacement could corrupt a third-party pin that coincidentally matches the current version. The drift is benign (uv sync/uv runre-locks it on next use); re-evaluate if that churn becomes noisy.
Alternatives considered
python-semantic-release— re-rejected (as in decision-019): commitizen already does the job; a second tool would split versioning authority.- TypeScript
semantic-release(alter-ego's tool) — excluded by the issue itself; would also add a Node release dependency to a Python-tooled repo. cz bump --check-consistencyinrelease.yml— catches drift only when a release is already running (and fails the release); the PR-time guard catches it on the offending PR instead. Also, consistency-at-bump only checks found-at-least-once, which would pass a half-updated marketplace manifest.- A dedicated pre-commit hook for the guard — equivalent coverage, but requires touching
.pre-commit-config.yaml; riding the existing pytest hook needs no pipeline edits at all.