mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-05-13 10:36:38 +02:00
* 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>
150 lines
7.1 KiB
Markdown
150 lines
7.1 KiB
Markdown
# Versioning & Release Strategy
|
|
|
|
GSD follows [Semantic Versioning 2.0.0](https://semver.org/) 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)
|
|
|
|
```bash
|
|
# 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
|
|
```
|