Files
get-shit-done/VERSIONING.md
Tom Boucher 53cda93a01 Add automated cherry-pick + SDK-bundle parity to hotfix flow (#2956)
* feat(workflows): hotfix auto-cherry-pick + SDK-bundle parity (#2955)

hotfix.yml:
- create: auto-cherry-picks fix:/chore: commits from origin/main since
  BASE_TAG, oldest-first. Patch-equivalents skipped via git cherry.
  feat:/refactor: never auto-included. Conflicts halt with offending SHA.
- finalize: install-smoke gate, sdk-bundle/gsd-sdk.tgz parity with
  release-sdk.yml, tightened next dist-tag re-point, --latest on gh
  release create. SDK package.json bumped in lockstep.

release-sdk.yml:
- New action input (publish | hotfix) and auto_cherry_pick boolean.
- New prepare job branches hotfix/X.YY.Z from highest vX.YY.* tag,
  cherry-picks same logic as hotfix.yml, outputs effective ref.
- install-smoke and release consume prepare.outputs.ref.
- Hotfix mode forces tag=latest, opens merge-back PR. Idempotent if
  branch already exists.

VERSIONING.md: documents the cumulative-tag invariant
(vX.YY.Z anchors vX.YY.{Z+1}) and both workflow paths.

Closes #2955

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(code-review): wire --fix dispatch and update stale command references (#2947)

* fix(#2893): surface non-canonical plan filenames instead of silently returning zero plans

Reporter saw `plan_count: 0` from `/gsd:execute-phase` even though five
plan files existed on disk. Investigation showed the planner had written
files like `01-PLAN-01-foundation.md`, while `phase-plan-index`'s strict
filter (`f.endsWith('-PLAN.md') || f === 'PLAN.md'`) rejected them
silently — collapsing two distinct states into the same `plans: []`
return:

  - directory truly has no plans (legit empty)
  - directory has plans but the filter rejected them (user/agent error)

The canonical contract is documented in three places:
  - `agents/gsd-planner.md` write_phase_prompt step (lines 1063-1080)
  - `commands/gsd/plan-phase.md`
  - `references/universal-anti-patterns.md` (rule 26)

It mandates `{padded_phase}-{NN}-PLAN.md` and explicitly forbids
`PLAN-NN.md` / `01-PLAN-01.md` / `plan-NN.md` etc. The strict filter is
correct per that contract. The bug is that the executor never tells the
user when the contract was violated — they just see `plan_count: 0`
with no signal.

Fix: add a diagnostic helper `describeNonCanonicalPlans()` that scans
the phase directory for files matching `*PLAN*.md` (the diagnostic net)
that the canonical filter rejected, excluding legit derivatives like
`*-PLAN-OUTLINE.md` and `*-PLAN.pre-bounce.md`. When offenders exist,
return a `warning` field naming each one and citing the canonical
pattern so the user knows what to rename to.

Wired into the three filter sites:
  - `phase-plan-index` (the executor's main entry point)
  - `phases list --type plans`
  - `find-phase`

The strict filter itself is unchanged — existing canonical plans behave
identically. This is purely a diagnostic that converts silent-empty
into loud-with-actionable-error.

Tests:
  - `phase-plan-index returns warning for reporter's exact filename
    pattern (`01-PLAN-01-foundation.md`)`
  - `truly empty dir does not emit a warning`
  - `canonical plans + outline + pre-bounce files do not emit a warning`

Closes #2893

* test(#2893): add parity tests for find-phase and phases list --type plans warnings

CodeRabbit's only finding on the prior commit: I wired the warning into
three filter sites (`phase-plan-index`, `find-phase`,
`phases list --type plans`) but only `phase-plan-index` had test
coverage for the warning shape. The other two paths could silently
diverge during future refactors — exactly the silent-drift class of bug
this fix exists to prevent.

Add four parity tests mirroring the existing two:

  - find-phase: non-canonical filenames produce a warning naming each
    offender + citing the canonical pattern.
  - find-phase: canonical plan + derivative files (PLAN-OUTLINE,
    pre-bounce) produce no warning.
  - phases list --type plans: same non-canonical case, but assert the
    warning is prefixed with `${dir}: ` (this path aggregates across
    phase directories so each offender is tagged with its dir).
  - phases list --type plans: canonical case, no warning.

`node --test tests/phase.test.cjs`: 98/98 pass (was 94, +4 new).

* docs(changelog): hotfix flow auto-cherry-pick + SDK bundle parity (#2955)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(workflows): address CodeRabbit findings on hotfix flow (#2955)

5 findings, all real:

1. BASE_TAG selection used lexicographic awk compare, breaking on
   multi-digit patches (v1.27.10 wrongly < v1.27.2). Fixed in both
   hotfix.yml and release-sdk.yml: append TARGET_TAG to candidate list,
   sort -V, take preceding entry. Semver-correct.

2,4. Cherry-pick conflict aborted locally with no remote branch to
   resolve from. Now the skeleton branch is pushed up-front (real runs);
   on conflict we abort, push the partial-pick state with
   --force-with-lease, and emit operator instructions in the run summary.

3. release-sdk.yml dry_run exited before cherry-pick, defeating the
   purpose. Now dry_run still applies cherry-picks locally (catches
   conflicts), just skips push. Downstream install-smoke runs against
   BASE_TAG; the cherry-pick verification itself is the dry-run signal.

5. release-sdk.yml release job missing pull-requests: write — gh pr
   create for the merge-back PR would have failed under restricted
   token defaults. Permission added.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(workflows): CR round 2 — dry-run signal + post-publish reconciliation (#2955)

3 findings, all real:

6. hotfix.yml create dry_run skipped every step (branch creation,
   cherry-pick, version bump) — a green dry-run gave no signal at all.
   Now the local checkout/cherry-pick/bump always runs; only the git
   push calls are gated on dry_run. Conflicts surface in dry-run too.

7,8. "Refuse if version already on npm" preflight hard-failed reruns,
   so a transient failure between npm publish and a later step (tag
   push, GH release, merge-back PR, dist-tag re-point) left the release
   half-shipped with no path to reconcile. Replaced with a
   prior_publish detect step that warns and sets skip_publish=true; the
   publish step is gated on that flag, but tag/release/PR/dist-tag
   continue. GitHub Release create is now idempotent (edit --latest if
   already exists).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(workflows): CR round 3 — preserve dry-run cherry-pick history in conflict guidance (#2955)

Dry-run conflict path discarded successful picks with the runner, but
the message told operators to rerun with auto_cherry_pick=false — which
recreates the branch from BASE_TAG and silently loses every pick that
had succeeded before the conflict.

Updated both hotfix.yml and release-sdk.yml: dry-run conflict summary
now lists the lost SHAs and recommends re-running with
auto_cherry_pick=true (real, not dry-run) to materialize the partial
branch on origin. Real-run guidance unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 11:51:45 -04:00

7.1 KiB

Versioning & Release Strategy

GSD follows Semantic Versioning 2.0.0 with three release tiers mapped to npm dist-tags.

Release Tiers

Tier What ships Version format npm tag Branch Install
Patch Bug fixes only 1.27.1 latest hotfix/1.27.1 npx get-shit-done-cc@latest
Minor Fixes + enhancements 1.28.0 latest (after RC) release/1.28.0 npx get-shit-done-cc@next (RC)
Major Fixes + enhancements + features 2.0.0 latest (after beta) release/2.0.0 npx get-shit-done-cc@next (beta)

npm Dist-Tags

Only two tags, following Angular/Next.js convention:

Tag Meaning Installed by
latest Stable production release npm install get-shit-done-cc (default)
next Pre-release (RC or beta) npm install get-shit-done-cc@next (opt-in)

The version string (-rc.1 vs -beta.1) communicates stability level. Users never get pre-releases unless they explicitly opt in.

Semver Rules

Increment When Examples
PATCH (1.27.x) Bug fixes, typo corrections, test additions Hook filter fix, config corruption fix
MINOR (1.x.0) Non-breaking enhancements, new commands, new runtime support New workflow command, discuss-mode feature
MAJOR (x.0.0) Breaking changes to config format, CLI flags, or runtime API; new features that alter existing behavior Removing a command, changing config schema

Pre-Release Version Progression

Major and minor releases use different pre-release types:

Minor: 1.28.0-rc.1  →  1.28.0-rc.2  →  1.28.0
Major: 2.0.0-beta.1 →  2.0.0-beta.2 →  2.0.0
  • beta (major releases only): Feature-complete but not fully tested. API mostly stable. Used for major releases to signal a longer testing cycle.
  • rc (minor releases only): Production-ready candidate. Only critical fixes expected.
  • Each version uses one pre-release type throughout its cycle. The rc action in the release workflow automatically selects the correct type based on the version.

Branch Structure

main                              ← stable, always deployable
  │
  ├── hotfix/1.27.1               ← patch: cherry-pick fix from main, publish to latest
  │
  ├── release/1.28.0              ← minor: accumulate fixes + enhancements, RC cycle
  │     ├── v1.28.0-rc.1          ← tag: published to next
  │     └── v1.28.0               ← tag: promoted to latest
  │
  ├── release/2.0.0               ← major: features + breaking changes, beta cycle
  │     ├── v2.0.0-beta.1         ← tag: published to next
  │     ├── v2.0.0-beta.2         ← tag: published to next
  │     └── v2.0.0                ← tag: promoted to latest
  │
  ├── fix/1200-bug-description    ← bug fix branch (merges to main)
  ├── feat/925-feature-name       ← feature branch (merges to main)
  └── chore/1206-maintenance      ← maintenance branch (merges to main)

Release Workflows

Patch Release (Hotfix)

For fixes that need to ship without waiting for the next minor.

A hotfix vX.YY.Z cumulatively includes everything in vX.YY.{Z-1} plus every fix:/chore: commit landed on main since that base. The base tag is the anchor — git cherry $BASE_TAG main reveals exactly which commits are still unshipped, and the new vX.YY.Z tag becomes the next hotfix's base, so the cycle is self-documenting.

Two paths

Path A — hotfix.yml (canonical, two-step):

  1. Trigger hotfix.yml with action=create, version=1.27.1, auto_cherry_pick=true (default).
    • Workflow detects BASE_TAG = highest v1.27.* < v1.27.1 (so 1.27.1 branches from v1.27.0; 1.27.2 would branch from v1.27.1).
    • Branches hotfix/1.27.1 from BASE_TAG.
    • Auto-cherry-picks every fix:/chore: commit on origin/main not already in the base, oldest-first. Patch-equivalents are skipped via git cherry. feat:/refactor: are never auto-included.
    • On conflict the workflow halts with the offending SHA. Resolve manually on the branch, then re-run finalize with auto_cherry_pick=false.
    • Bumps package.json (and sdk/package.json), pushes the branch, and lists every included SHA in the run summary.
  2. (Optional) push additional manual commits to hotfix/1.27.1.
  3. Trigger hotfix.yml with action=finalize. The workflow:
    • Runs install-smoke cross-platform gate.
    • Runs full test suite + coverage.
    • Builds SDK, bundles sdk-bundle/gsd-sdk.tgz inside the CC tarball (parity with release-sdk.yml).
    • Tags v1.27.1, publishes to @latest, re-points @next → v1.27.1.
    • Opens merge-back PR against main.

Path B — release-sdk.yml (stopgap, one-shot):

Active while the @gsd-build/sdk npm token is unavailable; bundles the SDK inside the CC tarball.

  1. Trigger release-sdk.yml with action=hotfix, version=1.27.1, auto_cherry_pick=true.
    • The prepare job creates the branch and cherry-picks (same logic as Path A).
    • install-smoke runs against the new branch.
    • The release job tags, publishes to @latest, re-points @next, opens merge-back PR.
    • Idempotent: if hotfix/1.27.1 already exists (e.g. you ran hotfix.yml create first), the prepare job checks it out and re-runs cherry-pick as a no-op.
  2. dry_run=true exercises the full pipeline without pushing the branch or publishing.

Minor Release (Standard Cycle)

For accumulated fixes and enhancements.

  1. Trigger release.yml with action create and version (e.g., 1.28.0)
  2. Workflow creates release/1.28.0 branch from main, bumps package.json
  3. Trigger release.yml with action rc to publish 1.28.0-rc.1 to next
  4. Test the RC: npx get-shit-done-cc@next
  5. If issues found: fix on release branch, publish rc.2, rc.3, etc.
  6. Trigger release.yml with action finalize — publishes 1.28.0 to latest
  7. Merge release branch to main

Major Release

Same as minor but uses -beta.N instead of -rc.N, signaling a longer testing cycle.

  1. Trigger release.yml with action create and version (e.g., 2.0.0)
  2. Trigger release.yml with action rc to publish 2.0.0-beta.1 to next
  3. If issues found: fix on release branch, publish beta.2, beta.3, etc.
  4. Trigger release.yml with action finalize -- publishes 2.0.0 to latest
  5. Merge release branch to main

Conventional Commits

Branch names map to commit types:

Branch prefix Commit type Version bump
fix/ fix: PATCH
feat/ feat: MINOR
hotfix/ fix: PATCH (immediate)
chore/ chore: none
docs/ docs: none
refactor/ refactor: none

Publishing Commands (Reference)

# Stable release (sets latest tag automatically)
npm publish

# Pre-release (must use --tag to avoid overwriting latest)
npm publish --tag next

# Verify what latest and next point to
npm dist-tag ls get-shit-done-cc