- Introduction
- Format Specification
- Component Definitions
- PRERELEASE Tags
- Version Ordering
- Use Cases and Examples
- Best Practices
- Implementation Guidelines
- Comparison with Other Systems
S4 Versioning System is a comprehensive versioning scheme that combines semantic versioning principles with additional metadata to provide complete traceability and context for software releases.
- Semantic: MAJOR.MINOR.PATCH versioning
- Stage: PRERELEASE stage identifier
- Stamp: YYYYMMDD.HHMM timestamp
- Sequence: BUILD number and COMMIT hash
- Traceability: Every version can be traced to exact source code
- Chronology: Clear temporal ordering of releases
- Context: Understand the stage and purpose of each release
- Automation: Easy to generate and parse programmatically
- Standards Compliance: Follows ISO 8601 for dates and SemVer conventions
MAJOR.MINOR.PATCH-PRERELEASE.BUILD+YYYYMMDD.HHMM.COMMIT.BRANCH
1.0.0-stable.1+20250127.2145.a3f5b2c.main
│ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ └─ BRANCH: Git branch name
│ │ │ │ │ │ │ └───────── COMMIT: Git commit hash (7 chars)
│ │ │ │ │ │ └────────────── HHMM: Time in 24-hour format
│ │ │ │ │ └─────────────────────── YYYYMMDD: Date (ISO 8601)
│ │ │ │ └────────────────────────── + : Metadata separator
│ │ │ └─────────────────────────────── BUILD: Sequential build number
│ │ └──────────────────────────────────── - : Prerelease separator
│ └────────────────────────────────────── MINOR: Minor version
└──────────────────────────────────────── MAJOR: Major version
- Components are separated by
.(dot) - PRERELEASE is preceded by
-(hyphen) - Metadata section starts with
+(plus sign) - All components are mandatory
- No whitespace allowed
- Case-sensitive for PRERELEASE and BRANCH
- Type: Non-negative integer
- Range: 0 to ∞
- Purpose: Incompatible API changes or breaking changes
- Initial Value: 0 for development, 1 for first stable release
When to Increment:
- Breaking changes to public API
- Major architecture changes
- Removal of deprecated features
- Incompatible database schema changes
Rules:
- Reset MINOR and PATCH to 0 when MAJOR is incremented
- Example:
1.5.3→2.0.0
- Type: Non-negative integer
- Range: 0 to ∞
- Purpose: New features added in backwards-compatible manner
- Initial Value: 0
When to Increment:
- New functionality added
- Significant improvements
- Deprecation of features (not removal)
- Large internal changes that don't break compatibility
Rules:
- Reset PATCH to 0 when MINOR is incremented
- Example:
1.2.5→1.3.0
- Type: Non-negative integer
- Range: 0 to ∞
- Purpose: Backwards-compatible bug fixes
- Initial Value: 0
When to Increment:
- Bug fixes
- Security patches
- Performance improvements
- Documentation updates (in some cases)
Rules:
- Increment for each bug fix release
- Example:
1.2.3→1.2.4
- Type: Lowercase alphanumeric string
- Purpose: Indicates the release stage
- Format:
[a-z]+(lowercase letters only)
Standard Values:
dev- Active developmentalpha- Alpha testingbeta- Beta testingrc- Release candidatestable- Production releasehotfix- Emergency fixpatch- Maintenance patchlts- Long-term support
See PRERELEASE Tags section for complete list.
- Type: Positive integer
- Range: 1 to ∞
- Purpose: Sequential counter for builds on the same day
- Initial Value: 1
When to Increment:
- Each new build of the same version on the same day
- Resets to 1 when date changes or version changes
Example:
1.0.0-stable.1+20250127.1200.a3f5b2c.main ← First build
1.0.0-stable.2+20250127.1530.b4c8e3d.main ← Second build (same day)
1.0.0-stable.1+20250128.0900.c5d9f4e.main ← Next day (reset to 1)
- Type: 8-digit date
- Format: ISO 8601 basic format
- Purpose: Date of build/release
- Range:
- YYYY: 1000-9999
- MM: 01-12
- DD: 01-31
Example: 20250127 = January 27, 2025
- Type: 4-digit time
- Format: 24-hour time
- Purpose: Time of build/release
- Range:
- HH: 00-23
- MM: 00-59
Example: 2145 = 21:45 (9:45 PM)
- Type: 7-character hexadecimal string
- Format: Short Git commit hash
- Purpose: Exact source code identifier
- Character Set:
[0-9a-f](lowercase)
How to Generate:
git rev-parse --short=7 HEADExample: a3f5b2c
- Type: String
- Format: Valid Git branch name
- Purpose: Identifies the source branch
- Allowed Characters:
[a-zA-Z0-9._/-]
Common Values:
main- Main production branchdevelop- Development branchfeature-<name>- Feature brancheshotfix-<name>- Hotfix branchesrelease- Release preparation branch
Example: main, feature-auth, hotfix-security
| Tag | Stage | Stability | Purpose | Audience |
|---|---|---|---|---|
dev |
Development | Unstable | Active development | Developers |
alpha |
Alpha | Unstable | Early testing | Internal testers |
beta |
Beta | Moderate | Feature testing | External testers |
rc |
Release Candidate | Stable | Pre-release | QA team |
stable |
Stable | Stable | Production | Public |
| Tag | Purpose | Use Case |
|---|---|---|
hotfix |
Emergency fixes | Critical bugs in production |
patch |
Maintenance | Minor updates |
lts |
Long-term support | Maintained versions |
| Tag | Purpose | Use Case |
|---|---|---|
canary |
Bleeding edge | Experimental features |
preview |
Public preview | Early access features |
nightly |
Nightly builds | Automated daily builds |
snapshot |
Point-in-time | Development snapshots |
experimental |
Experimental | Unstable features |
Development Flow:
dev → alpha → beta → rc → stable
Maintenance Flow:
stable → hotfix → stable
stable → patch → stable
Experimental Flow:
dev → canary → dev → alpha
Versions are ordered by the following hierarchy:
- MAJOR (numerical, ascending)
- MINOR (numerical, ascending)
- PATCH (numerical, ascending)
- PRERELEASE (stage precedence)
- TIMESTAMP (chronological)
- BUILD (numerical, ascending)
dev < alpha < beta < rc < stable
Special tags follow alphabetical ordering relative to standard tags.
# Version ordering (ascending):
0.1.0-dev.1+20250127.1200.a1b2c3d.main
0.1.0-alpha.1+20250128.0900.b2c3d4e.main
0.1.0-beta.1+20250130.1000.c3d4e5f.main
0.1.0-rc.1+20250201.1200.d4e5f6a.main
0.1.0-stable.1+20250205.1500.e5f6a7b.main
0.2.0-stable.1+20250210.0900.f6a7b8c.main
1.0.0-stable.1+20250215.1000.a7b8c9d.main
def compare_versions(v1, v2):
# Compare MAJOR.MINOR.PATCH
if v1.major != v2.major:
return v1.major - v2.major
if v1.minor != v2.minor:
return v1.minor - v2.minor
if v1.patch != v2.patch:
return v1.patch - v2.patch
# Compare PRERELEASE
stage_order = ['dev', 'alpha', 'beta', 'rc', 'stable']
if v1.prerelease != v2.prerelease:
return stage_order.index(v1.prerelease) - stage_order.index(v2.prerelease)
# Compare TIMESTAMP (YYYYMMDD.HHMM)
if v1.timestamp != v2.timestamp:
return v1.timestamp - v2.timestamp
# Compare BUILD
return v1.build - v2.build
0.1.0-dev.1+20250110.1000.a1b2c3d.develop
0.1.0-dev.2+20250110.1430.b2c3d4e.develop
0.1.0-dev.3+20250111.0900.c3d4e5f.develop
0.2.0-dev.1+20250115.1200.d4e5f6a.feature-auth
0.2.0-dev.2+20250115.1600.e5f6a7b.feature-auth
0.2.0-dev.1+20250116.1000.f6a7b8c.feature-api
0.3.0-alpha.1+20250120.0900.a7b8c9d.main
0.3.0-alpha.2+20250120.1400.b8c9d0e.main
0.3.0-alpha.3+20250121.1000.c9d0e1f.main
0.4.0-beta.1+20250125.1000.d0e1f2a.release
0.4.0-beta.2+20250126.1500.e1f2a3b.release
1.0.0-rc.1+20250128.1200.f2a3b4c.release
1.0.0-rc.2+20250129.0900.a3b4c5d.release
1.0.0-stable.1+20250201.1000.b4c5d6e.main
1.0.1-stable.1+20250205.1400.c5d6e7f.main
1.0.2-hotfix.1+20250206.1800.d6e7f8a.hotfix-critical
1.1.0-stable.1+20250210.0900.e7f8a9b.main
2.0.0-stable.1+20250220.1000.f8a9b0c.main
✅ DO:
- Follow the standard progression: dev → alpha → beta → rc → stable
- Increment BUILD for same-day releases of same version
- Use meaningful branch names
- Tag releases in Git
❌ DON'T:
- Skip stages arbitrarily
- Go backwards in stages (e.g., stable → beta)
- Reuse version numbers
- Change COMMIT or BRANCH in same version
✅ DO:
- Use
mainfor production releases - Use
developfor development - Use descriptive feature branch names (
feature-auth,feature-api) - Use specific hotfix names (
hotfix-security,hotfix-memory-leak)
❌ DON'T:
- Use generic branch names (
fix,test,new) - Include spaces or special characters
- Use uppercase letters inconsistently
✅ DO:
- Use
devfor daily development work - Use
alphafor internal testing - Use
betafor external testing - Use
rcfor release candidates - Use
stablefor production releases - Use
hotfixfor emergency fixes
❌ DON'T:
- Mix prerelease meanings
- Create custom tags without documentation
- Use
stablefor untested code
✅ DO:
- Start BUILD at 1 for each new day
- Increment BUILD for each same-day release
- Reset BUILD to 1 when version changes
❌ DON'T:
- Skip build numbers
- Use BUILD as a global counter
- Reuse build numbers on different days
✅ DO:
- Use actual build/release time
- Use UTC or document timezone
- Ensure clocks are synchronized in CI/CD
❌ DON'T:
- Use arbitrary timestamps
- Mix timezones
- Backdate releases
✅ DO:
- Tag each release in Git
- Use annotated tags:
git tag -a v1.0.0-stable.1+20250127.2145.a3f5b2c.main - Include release notes in tag messages
- Push tags to remote:
git push --tags
❌ DON'T:
- Forget to tag releases
- Delete or modify existing tags
- Use lightweight tags
A proper S4 implementation should:
- Read current version from a version file or Git tags
- Determine version bump (major/minor/patch)
- Select appropriate PRERELEASE tag
- Auto-increment BUILD for same-day releases
- Generate timestamp automatically
- Extract COMMIT hash from Git
- Detect BRANCH from Git
- Update version files in the project
- Create Git tag with the new version
- Commit changes to version control
project/
├── VERSION # Contains current S4 version
├── version.json # Machine-readable version info
├── CHANGELOG.md # Version history
└── .version-history/ # Optional: detailed logs
VERSION file example:
1.0.0-stable.1+20250127.2145.a3f5b2c.main
version.json example:
{
"version": "1.0.0-stable.1+20250127.2145.a3f5b2c.main",
"major": 1,
"minor": 0,
"patch": 0,
"prerelease": "stable",
"build": 1,
"date": "20250127",
"time": "2145",
"commit": "a3f5b2c",
"branch": "main"
}Recommended CLI interface:
# Bump patch version
s4 bump patch
# Bump minor version
s4 bump minor
# Bump major version
s4 bump major
# Change prerelease stage
s4 stage alpha
s4 stage beta
s4 stage rc
s4 stage stable
# Manual version set
s4 set 2.0.0-stable
# Show current version
s4 current
# Show version history
s4 historyExample GitHub Actions workflow:
name: Version and Release
on:
push:
branches: [ main ]
jobs:
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Bump version
run: |
./scripts/s4-bump.sh patch stable
- name: Create release
run: |
VERSION=$(cat VERSION)
gh release create "v$VERSION" --notes "Release $VERSION"The S4 system should have parsers/generators for:
- Python
- JavaScript/Node.js
- Go
- Rust
- Java
- C/C++
- Shell (Bash/Zsh)
| Feature | SemVer | S4 |
|---|---|---|
| Format | MAJOR.MINOR.PATCH | MAJOR.MINOR.PATCH-PRERELEASE.BUILD+YYYYMMDD.HHMM.COMMIT.BRANCH |
| Prerelease | Optional | Required |
| Metadata | Optional | Required |
| Traceability | Low | High |
| Chronology | No | Yes |
| Git Integration | No | Yes |
When to use SemVer: Simple libraries, minimal requirements
When to use S4: Complex projects, need for traceability
| Feature | CalVer | S4 |
|---|---|---|
| Date-based | Yes | Partial (metadata) |
| Semantic | No | Yes |
| Flexibility | High | Medium |
| Meaning | Date = importance | Version = compatibility |
When to use CalVer: Time-based releases (Ubuntu, etc.)
When to use S4: API versioning, libraries, applications
| Feature | Git Hash | S4 |
|---|---|---|
| Uniqueness | Perfect | High |
| Readability | Poor | Good |
| Meaning | None | Full context |
| Ordering | No | Yes |
When to use Git Hash: Internal references
When to use S4: User-facing versions, releases
Complete S4 version regex:
^([0-9]+)\.([0-9]+)\.([0-9]+)-([a-z]+)\.([0-9]+)\+([0-9]{8})\.([0-9]{4})\.([0-9a-f]{7})\.([a-zA-Z0-9._/-]+)$Capture Groups:
- MAJOR
- MINOR
- PATCH
- PRERELEASE
- BUILD
- DATE (YYYYMMDD)
- TIME (HHMM)
- COMMIT
- BRANCH
A valid S4 version must satisfy:
- ✅ MAJOR, MINOR, PATCH are non-negative integers
- ✅ PRERELEASE contains only lowercase letters
- ✅ BUILD is a positive integer (≥ 1)
- ✅ DATE is valid (YYYYMMDD format, valid calendar date)
- ✅ TIME is valid (HHMM format, HH: 00-23, MM: 00-59)
- ✅ COMMIT is 7 lowercase hexadecimal characters
- ✅ BRANCH is a valid Git branch name
- ✅ All components are present (no optional parts)
SemVer: 1.2.3-beta
S4: 1.2.3-beta.1+20250127.2145.a3f5b2c.main
Migration steps:
- Keep MAJOR.MINOR.PATCH unchanged
- Normalize PRERELEASE tag (lowercase)
- Add BUILD number (start with 1)
- Add current timestamp
- Add current Git commit hash
- Add current Git branch
CalVer: 2025.01.3
S4: 0.1.3-stable.1+20250127.2145.a3f5b2c.main
Migration steps:
- Decide on MAJOR.MINOR.PATCH mapping
- Add PRERELEASE tag (likely
stable) - Add BUILD, timestamp, commit, branch
A: Yes! S4 is perfect for libraries that need traceability.
A: Yes, BUILD is always required (use 1 if only one build).
A: Yes, but document them clearly and keep lowercase.
A: Use only alphanumeric, dots, underscores, hyphens, and slashes.
A: Use different branch names in the version string.
A: No, all components are required. Use a dummy hash like 0000000 if needed.
A: Document your choice, but UTC is recommended for consistency.
This versioning system is released into the public domain.
This is a living document. Suggestions and improvements are welcome.
Repository: (s41r4j/s4vs)[https://github.com/s41r4j/s4vs]