# Version Management

> Technology-agnostic version management system with Git tags as source of truth Use when this capability is needed.

- Skill: `tomevault-io/version-management` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/version-management`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/version-management/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/version-management

---


# Version Management

A technology-agnostic version management system using Git tags as the source of truth, with platform-specific adapters.

## Overview

**Design Philosophy:**
- Git tags are the **source of truth** for versions
- Committed version files (`version.properties`, `package.json`, etc.) cache the version for fast builds
- Release workflows update both git tags and version files
- Supports semantic versioning (major.minor.patch)

## Prerequisites

- Git repository initialized
- Git tags enabled
- Bash shell available
- Write access to project directory

## Inputs

| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| project_path | Yes | . | Project root directory |
| platform | Yes | gradle | Platform adapter: gradle, npm, python |

## Process

### Step 1: Create Core Version Manager Script

Create `scripts/version-manager.sh` (technology-agnostic):

```bash
#!/bin/bash
# Core version manager - technology agnostic
# Handles: tag parsing, semver calculation, version bumping

set -euo pipefail

# === CONFIGURATION ===
BASE_VERSION="${BASE_VERSION:-1.0}"
TAG_PREFIX="${TAG_PREFIX:-v}"

# === CORE FUNCTIONS ===

get_latest_version() {
    local prefix="${1:-$TAG_PREFIX}"
    git tag -l "${prefix}*" 2>/dev/null |
        sed "s/^${prefix}//" |
        sort -V |
        tail -1 || echo "0.0.0"
}

bump_version() {
    local version="$1"
    local bump_type="${2:-patch}"

    IFS='.' read -r major minor patch <<< "$version"
    major="${major:-0}"; minor="${minor:-0}"; patch="${patch:-0}"

    case "$bump_type" in
        major) major=$((major + 1)); minor=0; patch=0 ;;
        minor) minor=$((minor + 1)); patch=0 ;;
        patch) patch=$((patch + 1)) ;;
    esac

    echo "${major}.${minor}.${patch}"
}

generate_version() {
    local bump_type="${1:-patch}"
    local latest
    latest=$(get_latest_version)

    if [[ "$latest" == "0.0.0" ]]; then
        echo "${BASE_VERSION}.0"
    else
        bump_version "$latest" "$bump_type"
    fi
}

output_version() {
    local version="$1"
    local is_prerelease="${2:-false}"

    if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
        echo "version=$version" >> "$GITHUB_OUTPUT"
        echo "is-prerelease=$is_prerelease" >> "$GITHUB_OUTPUT"
        echo "tag=${TAG_PREFIX}${version}" >> "$GITHUB_OUTPUT"
    fi

    echo "$version"
}

main() {
    local command="${1:-generate}"
    local arg="${2:-patch}"

    case "$command" in
        generate) output_version "$(generate_version "$arg")" "false" ;;
        latest)   get_latest_version ;;
        bump)     bump_version "$(get_latest_version)" "$arg" ;;
        *)        echo "Usage: $0 {generate|latest|bump} [patch|minor|major]" >&2; exit 1 ;;
    esac
}

main "$@"
```

Make it executable:

```bash
chmod +x scripts/version-manager.sh
```

### Step 2: Create Platform Adapter

#### For Gradle/Android Projects

Create `scripts/gradle-version.sh`:

```bash
#!/bin/bash
# Gradle/Android version adapter

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/version-manager.sh"

# Convert semver to Android versionCode: "1.2.34" → 1002034
semver_to_version_code() {
    local version="$1"
    IFS='.' read -r major minor patch <<< "$version"
    echo $((major * 1000000 + minor * 1000 + patch))
}

# Update version.properties
update_version_properties() {
    local version="$1"
    local version_code
    version_code=$(semver_to_version_code "$version")

    cat > version.properties << VERSIONEOF
# Auto-generated by release workflow - do not edit manually
VERSION_NAME=$version
VERSION_CODE=$version_code
VERSIONEOF
    echo "Updated version.properties: VERSION_NAME=$version, VERSION_CODE=$version_code"
}

# Output Android-specific values to GitHub Actions
output_android_version() {
    local version="$1"
    local version_code
    version_code=$(semver_to_version_code "$version")

    if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
        echo "version-code=$version_code" >> "$GITHUB_OUTPUT"
    fi
}

main() {
    local command="${1:-generate}"
    shift || true

    case "$command" in
        generate)
            local version
            version=$(generate_version "${1:-patch}")
            output_version "$version" "false"
            output_android_version "$version"
            ;;
        update)
            local version="${1:-}"
            [[ -z "$version" ]] && version=$(get_latest_version)
            update_version_properties "$version"
            ;;
        version-code)
            local version="${1:-}"
            [[ -z "$version" ]] && version=$(get_latest_version)
            semver_to_version_code "$version"
            ;;
        *)
            # Delegate to core
            source "$SCRIPT_DIR/version-manager.sh"
            main "$command" "$@"
            ;;
    esac
}

main "$@"
```

Make it executable:

```bash
chmod +x scripts/gradle-version.sh
```

### Step 3: Create Initial Version File

For Gradle projects, create `version.properties`:

```bash
cat > version.properties << 'EOF'
# Auto-generated by release workflow - do not edit manually
VERSION_NAME=1.0.0
VERSION_CODE=1000000
EOF
```

### Step 4: Update Build Configuration

#### For Gradle Projects

Update `app/build.gradle.kts` to read from version.properties:

```kotlin
import java.util.Properties

// Read version from version.properties (fast, no git commands)
val versionProps = Properties().apply {
    val versionFile = rootProject.file("version.properties")
    if (versionFile.exists()) {
        versionFile.inputStream().use { load(it) }
    }
}

android {
    defaultConfig {
        versionName = versionProps.getProperty("VERSION_NAME", "0.0.1-dev")
        versionCode = versionProps.getProperty("VERSION_CODE", "1")?.toIntOrNull() ?: 1
    }
}
```

**Detection logic:**
- Check if versionName/versionCode already set in build.gradle.kts
- If using a different version scheme, ask user if they want to migrate

### Step 5: Commit Version File

```bash
git add version.properties scripts/
git commit -m "chore: add version management system"
```

## Usage Examples

### Local Development

```bash
# Get current version
./scripts/version-manager.sh latest

# Generate next patch version
./scripts/version-manager.sh generate patch

# Generate next minor version
./scripts/version-manager.sh generate minor

# Generate next major version
./scripts/version-manager.sh generate major
```

### For Gradle Projects

```bash
# Generate version and update version.properties
./scripts/gradle-version.sh generate patch

# Update version.properties with specific version
./scripts/gradle-version.sh update 1.2.3

# Get version code for a version
./scripts/gradle-version.sh version-code 1.2.3
```

### In GitHub Actions

See the release workflow created by `android-playstore-setup` skill for full integration example.

## Verification

**MANDATORY:** Run these commands:

```bash
# Verify scripts exist
test -f scripts/version-manager.sh && echo "✓ Core script exists"
test -f scripts/gradle-version.sh && echo "✓ Gradle adapter exists"

# Verify scripts are executable
test -x scripts/version-manager.sh && echo "✓ Core script is executable"
test -x scripts/gradle-version.sh && echo "✓ Gradle adapter is executable"

# Test version generation
./scripts/version-manager.sh latest
./scripts/version-manager.sh generate patch
```

**Expected output:**
- ✓ Core script exists
- ✓ Gradle adapter exists
- ✓ Core script is executable
- ✓ Gradle adapter is executable
- Version numbers displayed

## Outputs

| Output | Location | Description |
|--------|----------|-------------|
| Core script | scripts/version-manager.sh | Technology-agnostic version logic |
| Gradle adapter | scripts/gradle-version.sh | Android-specific version handling |
| Version cache | version.properties | Cached version for fast builds |
| Build config | app/build.gradle.kts | Updated to read from version file |

## Troubleshooting

### "No tags found"
**Cause:** No git tags exist yet
**Fix:** First version will be 1.0.0 (or BASE_VERSION.0)

### "version.properties not found"
**Cause:** File not created or committed
**Fix:** Run `./scripts/gradle-version.sh update 1.0.0` to create it

### "Permission denied"
**Cause:** Scripts not executable
**Fix:** `chmod +x scripts/*.sh`

## Platform Adapters

### NPM (Future)

For npm projects, create `scripts/npm-version.sh` that updates `package.json`:

```bash
# Updates package.json with new version
update_package_json() {
    local version="$1"
    npm version "$version" --no-git-tag-version
}
```

### Python (Future)

For Python projects, create `scripts/python-version.sh` that updates `__version__`:

```bash
# Updates __init__.py with new version
update_python_version() {
    local version="$1"
    sed -i "s/__version__ = .*/__version__ = \"$version\"/" src/__init__.py
}
```

## Completion Criteria

- [ ] `scripts/version-manager.sh` exists and is executable
- [ ] Platform adapter script exists and is executable
- [ ] Version file created (e.g., `version.properties`)
- [ ] Build configuration updated to read from version file
- [ ] Scripts can generate and bump versions correctly
- [ ] `git tag -l` shows version tags (after first release)

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/hitoshura25) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-13 -->

