This document defines how versions are managed across the affinity-api-x repository.
| Component | Version Source | Versioning |
|---|---|---|
| SDK + CLI | pyproject.toml |
Single source of truth, SemVer |
| SDK Plugin | plugins/affinity-sdk/.claude-plugin/plugin.json |
Independent, SemVer |
| CLI Plugin | plugins/xaffinity-cli/.claude-plugin/plugin.json |
Independent, SemVer |
| MCP Server | mcp/VERSION |
Independent, declares CLI compatibility |
| MCP Plugin | mcp/.claude-plugin/plugin.json |
Independent, SemVer |
We follow SemVer 2.0.0. Given version MAJOR.MINOR.PATCH:
- Removing or renaming a public SDK class/method
- Changing CLI command names or required arguments
- Changing JSON output structure in incompatible ways
- Removing CLI flags or options
- Adding new SDK classes/methods
- Adding new CLI commands or optional flags
- Adding new fields to JSON output (additive)
- Performance improvements with no API changes
- Fixing bugs without changing the API
- Documentation improvements
- Internal refactoring with no external impact
Plugins (SDK, CLI, MCP) are distributed via Git marketplaces, not PyPI. Their versions
are independent from the SDK/CLI version in pyproject.toml. This follows
Anthropic's plugin guidance:
update the plugin version before distributing changes.
Plugins contain hooks, skills, and configuration that change independently from the SDK/CLI Python code. Examples:
- A security fix to a hook script changes plugin behavior but not the SDK
- A skill rewrite improves LLM guidance but doesn't touch Python code
- Metadata changes (descriptions, author) are plugin-only
Tying plugin versions to the SDK version would either:
- Trigger unnecessary PyPI releases for plugin-only changes, or
- Leave plugin changes unversioned (confusing for users and debugging)
| Change | Bump |
|---|---|
| Hook behavior change (e.g., security fix) | PATCH |
| Skill content rewrite | PATCH |
| New skill or hook added | MINOR |
| Skill or hook removed | MAJOR |
| Metadata-only changes (description, author) | No bump needed |
| Plugin | Version Source |
|---|---|
| SDK Plugin | plugins/affinity-sdk/.claude-plugin/plugin.json → version field |
| CLI Plugin | plugins/xaffinity-cli/.claude-plugin/plugin.json → version field |
| MCP Plugin | mcp/.claude-plugin/plugin.json → version field |
Plugin versions are not auto-synced from pyproject.toml. Update them manually
when plugin content changes. The pre-commit hook validates that plugin.json files
have valid JSON but does not enforce version sync.
- Plugin versions MAY diverge from the SDK version
- When an SDK release also changes plugin content, bump both
- Plugin cache invalidation is based on git commit hash, not version number, so version bumps are for human traceability, not distribution mechanics
SDK is currently at 0.x.y — per SemVer spec item 4:
"Major version zero (0.y.z) is for initial development. Anything MAY change at any time."
This means:
- MINOR bumps (0.6 → 0.7) MAY include breaking changes
- PATCH bumps (0.6.5 → 0.6.6) should be backwards-compatible
- MCP must track CLI minor version for compatibility
The MCP server shells out to CLI commands. These changes require MCP updates:
| CLI Change | MCP Impact | Action Required |
|---|---|---|
| New command added | None | Optional: add MCP tool |
| New optional flag | None | Optional: use new flag |
| JSON output field added | None | Backwards-compatible |
| JSON output field removed | Breaking | Update MCP, bump COMPATIBILITY |
| JSON output structure changed | Breaking | Update MCP, bump COMPATIBILITY |
| Command renamed | Breaking | Update MCP, bump COMPATIBILITY |
| Required flag added | Breaking | Update MCP, bump COMPATIBILITY |
Releases are triggered automatically when version files are updated on main.
The release detection workflow compares the current version in version files against existing release tags:
- After CI passes on
main, the release detection workflow runs - It reads the SDK version from
pyproject.tomland MCP version frommcp/VERSION - It checks if release tags exist:
- SDK: checks for
v{version}tag (e.g.,v0.9.2) - MCP: checks for
mcp-v{version}tag (e.g.,mcp-v1.7.6)
- SDK: checks for
- If no tag exists for the current version, it triggers the release workflow via
workflow_dispatchAPI
The release workflows are triggered via GitHub's workflow_dispatch API (not reusable workflows). This enables:
- PyPI attestations: Full provenance attestation support for all releases
- GITHUB_TOKEN: No PATs or GitHub Apps required
- Tag creation post-publish: Tags are created after successful PyPI publish (SDK) or GitHub release (MCP)
This approach is robust regardless of how many commits are pushed together, squash merges, rebases, or any other git workflow.
- Update version in
pyproject.toml - If plugin content also changed: bump plugin versions in their
plugin.jsonfiles - Run pre-commit
- Update
CHANGELOG.mdwith changes (see Changelog Format below) - If CLI output changed: check MCP compatibility
- Commit and push to
main(or merge PR) - Release runs automatically — tag created post-release
SDK releases include MCPB bundles (built from the same commit) for convenience, so users don't need to find separate MCP releases.
- Update
mcp/VERSION - If MCP plugin content also changed: bump version in
mcp/.claude-plugin/plugin.json - Update
mcp/CHANGELOG.md(see Changelog Format below) - If CLI requirements changed: update
mcp/COMPATIBILITY - Run pre-commit (syncs server.meta.json from
mcp/VERSION) - Commit and push to
main(or merge PR) - Release runs automatically — tag created post-release
You can also trigger releases via tags:
# SDK
git tag -a v0.9.1 -m "v0.9.1" && git push origin v0.9.1
# MCP
git tag -a mcp-v1.7.6 -m "MCP v1.7.6" && git push origin mcp-v1.7.6| Component | Tag Pattern | Example |
|---|---|---|
| SDK | vX.Y.Z |
v0.9.0 |
| MCP | mcp-vX.Y.Z |
mcp-v1.7.5 |
Before releasing a CLI change that modifies JSON output:
# 1. Build and install the new CLI locally
pip install -e .
# 2. Run MCP tools manually to verify they still work
cd mcp
./xaffinity-mcp.sh validate
# 3. Test specific tools
source lib/common.sh
run_xaffinity_readonly person ls --query "test" --output json --quiet| File | Purpose | Updated By |
|---|---|---|
pyproject.toml |
SDK/CLI version | Manual |
plugins/affinity-sdk/.claude-plugin/plugin.json |
SDK plugin version | Manual |
plugins/xaffinity-cli/.claude-plugin/plugin.json |
CLI plugin version | Manual |
mcp/VERSION |
MCP distribution version | Manual |
mcp/server.d/server.meta.json |
MCP server metadata | Pre-commit hook (from mcp/VERSION) |
mcp/.claude-plugin/plugin.json |
MCP plugin version | Manual |
mcp/COMPATIBILITY |
CLI version requirements | Manual |
mcp/mcpb.conf |
MCPB bundle config | Manual (version from VERSION) |
mcp/mcp-bash.lock |
MCP-bash framework version + commit hash | Manual |
mcp/mcp-publisher.lock |
MCP Registry publisher CLI version | Manual |
mcp/server.json |
MCP Registry metadata template (patched by CI) | Manual (version/hash patched at release time) |
The MCP server vendors the mcp-bash runtime at mcp/.mcp-bash/ (committed to git). Version and commit hash are also recorded in mcp/mcp-bash.lock for MCPB bundling.
# 1. Upgrade system mcp-bash to the new version
# 2. Re-vendor from the updated install:
cd mcp && mcp-bash vendor --upgrade
# 3. Update mcp/mcp-bash.lock with new version and commit hash:
git ls-remote https://github.com/yaniv-golan/mcp-bash-framework.git 'vX.Y.Z^{}'
# 4. Verify integrity:
mcp-bash vendor --verify
# 5. Review and commit:
git diff mcp/.mcp-bash/
git add mcp/.mcp-bash/ mcp/mcp-bash.lock
# 6. Update mcp/CHANGELOG.mdBoth CHANGELOG.md (SDK/CLI) and mcp/CHANGELOG.md follow Keep a Changelog
with one addition: every version entry must start with a ### Highlights section.
## [1.6.0] - 2026-02-16
### Highlights
1-3 sentences in plain language summarizing why users should upgrade.
Write for the user, not the developer — focus on what's now possible
or what problem is fixed, not internal implementation details.
### Added
- ...
### Fixed
- ...Why Highlights matter: The release workflow (tools/generate_release_notes.py) reads
the changelog and restructures it into GitHub Release notes. The Highlights section
becomes the first thing users see in release notifications and RSS feeds. Without it,
the release notes lack context.
Guidelines for writing Highlights:
- Use plain language ("You can now..." not "Changed
InteractionService.list()...") - For breaking changes, mention them with a Breaking: prefix and a brief migration hint
- For patch releases with only fixes, one sentence is fine
- For feature releases, summarize the 1-2 most impactful additions
- Version bumped in
pyproject.toml - If plugin content also changed: plugin versions bumped in their
plugin.jsonfiles - Pre-commit ran
-
CHANGELOG.mdupdated with### Highlightsand categorized changes - If CLI output changed: MCP tested and COMPATIBILITY checked
- Changes merged to
main - Verify release workflow completed successfully
- Version bumped in affected
plugin.jsonfile(s) -
CHANGELOG.mdupdated (under plugin section) - Pre-commit ran
- Changes merged to
main - No PyPI release triggered (SDK version unchanged)
- Version bumped in
mcp/VERSION - If MCP plugin content also changed: version bumped in
mcp/.claude-plugin/plugin.json -
mcp/CHANGELOG.mdupdated with### Highlightsand categorized changes - If CLI requirements changed:
mcp/COMPATIBILITYverified - Pre-commit ran (syncs server.meta.json)
- Changes merged to
main - Verify release workflow completed successfully
- Verify MCP Registry publish step succeeded (non-blocking; check workflow annotations)