Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .squad/decisions/inbox/flight-versioning-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Decision: Versioning Policy — No Prerelease Versions on dev/main

**By:** Flight (Lead)
**Date:** 2026-03-29
**Requested by:** Dina
**Status:** DECIDED
**Confidence:** Medium (confirmed by PR #640 incident, PR #116 prerelease leak, CI gate implementation)

## Decision

1. **All packages use strict semver** (`MAJOR.MINOR.PATCH`). No prerelease suffixes on `dev` or `main`.
2. **Prerelease versions are ephemeral.** `bump-build.mjs` creates `-build.N` for local testing only — never committed.
3. **SDK and CLI versions must stay in sync.** Divergence silently breaks npm workspace resolution.

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decision asserts “SDK and CLI versions must stay in sync” and that divergence breaks npm workspace resolution. Current repo docs and changeset configuration describe independent versioning (e.g., docs/blog/017-version-alignment.md and docs/src/content/docs/guide/contributing.md), and the documented workspace incident is about prerelease versions not matching the SDK dependency range, not about version divergence itself. Recommend rewording this bullet to focus on the prerelease-range mismatch (and only claim lockstep versioning if the repo is actually moving to a fixed/linked versioning model).

Suggested change
3. **SDK and CLI versions must stay in sync.** Divergence silently breaks npm workspace resolution.
3. **SDK and CLI dependency ranges must remain compatible.** Prerelease versions (e.g., `0.9.1-build.N`) that fall outside declared semver ranges can silently break npm workspace resolution and fall back to registry packages.

Copilot uses AI. Check for mistakes.
4. **Surgeon owns version bumps.** Other agents must not modify `version` fields in `package.json` unless fixing a prerelease leak.
5. **CI enforcement via `prerelease-version-guard`** blocks PRs with prerelease versions. `skip-version-check` label is Surgeon-only.

## Why

The repo had no documented versioning policy. This caused two incidents:

- **PR #640:** Prerelease version `0.9.1-build.4` silently broke workspace resolution. The semver range `>=0.9.0` does not match prerelease versions, causing npm to install a stale registry package instead of the local workspace link. Four PRs (#637–#640) patched symptoms before the root cause was found.
- **PR #116:** Surgeon set versions to `0.9.1-build.1` instead of `0.9.1` on a release branch because there was no guidance on what constitutes a clean release version.

## Skill Reference

Full policy documented in `.squad/skills/versioning-policy/SKILL.md`.

## Impact

- All agents must follow the versioning policy when touching `package.json`
- Surgeon charter should reference this skill for release procedures
- CI pipeline enforces the policy via automated gate
119 changes: 119 additions & 0 deletions .squad/skills/versioning-policy/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
name: "versioning-policy"
description: "Semver versioning rules for Squad SDK and CLI — prevents prerelease version incidents"
domain: "release, versioning, npm, CI"
confidence: "medium"
source: "earned (PR #640 workspace resolution incident, PR #116 prerelease leak, CI gate implementation)"
---

## Context

Squad is a monorepo with two publishable npm packages (`@bradygaster/squad-sdk` and `@bradygaster/squad-cli`) managed via npm workspaces. Version mismatches and prerelease leaks have caused production incidents — most notably PR #640, where a `-build.N` prerelease version silently broke workspace dependency resolution.

This skill codifies the versioning rules every agent must follow.

## 1. Version Format

All packages use **strict semver**: `MAJOR.MINOR.PATCH`

- ✅ `0.9.1`, `1.0.0`, `0.10.0`
- ❌ `0.9.1-build.4`, `0.9.1-preview.1`, `0.8.6.1-preview`

No prerelease suffixes on `dev` or `main` branches — ever.
Comment on lines +17 to +22

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In §1, 0.9.1-build.4 is shown under ❌ even though §2 explains these -build.N versions are intentionally produced locally by bump-build.mjs. Consider rewording the ✅/❌ examples to make it explicit that -build.N is valid semver but disallowed on dev/main / in committed diffs (rather than implying the format itself is invalid).

Suggested change
All packages use **strict semver**: `MAJOR.MINOR.PATCH`
- ✅ `0.9.1`, `1.0.0`, `0.10.0`
- ❌ `0.9.1-build.4`, `0.9.1-preview.1`, `0.8.6.1-preview`
No prerelease suffixes on `dev` or `main` branches — ever.
All packages use **strict semver** in committed `package.json` files on `dev` and `main`: `MAJOR.MINOR.PATCH`
- ✅ Allowed in committed `package.json` on `dev`/`main`: `0.9.1`, `1.0.0`, `0.10.0`
- ⚠️ Valid semver used **only for local testing** (never committed): `0.9.1-build.4` (see §2)
- ❌ Disallowed in committed `package.json` on `dev`/`main`: `0.9.1-preview.1`, `0.8.6.1-preview`
No prerelease suffixes in committed `package.json` files on `dev` or `main` branches — `-build.N` is for local-only testing (see §2).

Copilot uses AI. Check for mistakes.

## 2. Prerelease Versions Are Ephemeral

The `scripts/bump-build.mjs` script creates `-build.N` versions (e.g., `0.9.1-build.4`) for **local development testing only**.

Rules:
- `-build.N` versions are created automatically during local `npm run build`
- They are **never committed** to `dev` or `main`
- The script skips itself in CI (`CI=true` or `SKIP_BUILD_BUMP=1`)
- If you see a `-build.N` version in a PR diff, it is a bug — reject the PR

## 3. SDK and CLI Version Sync

Both `@bradygaster/squad-sdk` and `@bradygaster/squad-cli` **MUST have the same version** at all times. The root `package.json` version must also match.

`bump-build.mjs` enforces this by updating all three `package.json` files in lockstep (root + `packages/squad-sdk` + `packages/squad-cli`).

If versions diverge, workspace resolution silently breaks (see §4).
Comment on lines +36 to +40

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Section 3 states SDK/CLI/root versions MUST always be identical and that divergence breaks workspace resolution. This contradicts the repo’s existing “independent versioning via changesets” guidance (e.g., docs/blog/017-version-alignment.md and docs/src/content/docs/guide/contributing.md) and the fact that the CLI depends on the SDK via a range (>=0.9.0), so simple divergence doesn’t inherently break workspace linking. Suggest clarifying that the real footgun is prerelease versions (e.g., -build.N) not satisfying the dependency range, and either remove the “must be identical” rule or explicitly document it as a new policy (and update the rest of the repo to match).

Suggested change
Both `@bradygaster/squad-sdk` and `@bradygaster/squad-cli` **MUST have the same version** at all times. The root `package.json` version must also match.
`bump-build.mjs` enforces this by updating all three `package.json` files in lockstep (root + `packages/squad-sdk` + `packages/squad-cli`).
If versions diverge, workspace resolution silently breaks (see §4).
As a **project convention**, `@bradygaster/squad-sdk`, `@bradygaster/squad-cli`, and the root `package.json` version **SHOULD normally be kept aligned** for release tags on `dev` and `main`. This avoids confusion when correlating bugs, logs, and published artifacts.
`bump-build.mjs` helps enforce this convention by updating all three `package.json` files in lockstep (root + `packages/squad-sdk` + `packages/squad-cli`) during local version bumps.
Workspace resolution only breaks when the SDK version does **not** satisfy the CLI’s semver range (typically because the SDK is on a prerelease like `0.9.1-build.4` while the CLI depends on `>=0.9.0`), as explained in §4.

Copilot uses AI. Check for mistakes.

## 4. npm Workspace Semver Footgun

The CLI depends on the SDK via a workspace dependency with a semver range:

```json
"@bradygaster/squad-sdk": ">=0.9.0"
```

**Critical:** Per the semver specification, `>=0.9.0` does **NOT** match `0.9.1-build.4`.

Semver prerelease versions (anything with a `-` suffix) are only matched by ranges that explicitly reference the same `MAJOR.MINOR.PATCH` base with a prerelease comparator. A bare `>=0.9.0` range skips all prerelease versions.

**What happens:** When the local SDK has version `0.9.1-build.4`, npm's workspace resolution fails to match the `>=0.9.0` range. npm then **silently installs a stale published version** from the npm registry instead of using the local workspace link. The build succeeds but runs against old SDK code.

This is the root cause of the **PR #640 incident**, where workspace packages appeared linked but were actually running against stale registry versions.

## 5. Who Bumps Versions

**Surgeon (Release Manager) owns all version bumps.**

| Agent | May modify `version` in package.json? |
|-------|---------------------------------------|
| Surgeon | ✅ Yes — sole owner of version bumps |
| Any other agent | ❌ No — unless explicitly fixing a prerelease leak |

If you discover a prerelease version committed to `dev` or `main`, you may fix it (revert to the clean release version) without Surgeon's approval. This is a safety escape hatch, not a license to manage versions.

## 6. Version Bump Lifecycle

```
┌─────────────────────────────────────────────────────────┐
│ Development phase │
│ Versions stay at current release: 0.9.1 │
│ bump-build.mjs creates -build.N locally (not committed)│
├─────────────────────────────────────────────────────────┤
│ Pre-release testing │
│ bump-build.mjs → 0.9.1-build.1, -build.2, ... │
│ Local only. Never committed. Never pushed. │
├─────────────────────────────────────────────────────────┤
│ Release │
│ Surgeon bumps to next version (e.g., 0.9.2 or 0.10.0) │
│ Tags, publishes to npm registry │
├─────────────────────────────────────────────────────────┤
│ Post-release │
│ Versions stay at the new release version (e.g., 0.9.2) │
│ Development continues on clean version │
└─────────────────────────────────────────────────────────┘
```

## 7. CI Enforcement

The **`prerelease-version-guard`** CI gate blocks any PR to `dev` or `main` that contains prerelease version strings in `package.json` files.

- The gate scans all three `package.json` files for `-` in the version field
- PRs with prerelease versions **cannot merge** until the version is cleaned
- The `skip-version-check` label bypasses the gate — use **only** for the bump-build script's own PR (if applicable), and only with Surgeon's approval

## 8. Incident Reference — PR #640

**PR #640** is the cautionary tale for this entire policy.

**What happened:** Prerelease versions (`0.9.1-build.4`) were committed to a branch. The workspace dependency `>=0.9.0` failed to match the prerelease version per semver spec. npm silently installed a stale published SDK from the registry instead of linking the local workspace copy. Four PRs (#637–#640) attempted iterative patches before the root cause was identified.

**Root cause:** No versioning policy existed. Agents didn't know that prerelease versions break workspace resolution, or that only Surgeon should modify versions.

**Resolution:** This skill, the `prerelease-version-guard` CI gate, and the team decision to centralize version ownership under Surgeon.

## Quick Reference

| Rule | Summary |
|------|---------|
| Format | `MAJOR.MINOR.PATCH` — no prerelease on dev/main |
| Prerelease | `-build.N` is local-only, never committed |
| Sync | SDK + CLI + root must have identical versions |
| Ownership | Surgeon bumps versions; others don't touch them |
| CI gate | `prerelease-version-guard` blocks prerelease PRs |
| Escape hatch | Any agent may revert a prerelease leak to clean version |
| Footgun | `>=0.9.0` does NOT match `0.9.1-build.4` per semver |
Loading