Skip to content

Proposal: platform-agnostic versioning for /ship (no-VERSION-file projects) #1474

@dep0we

Description

@dep0we

Hi — opening this as a design discussion (not a PR yet). I'm running gstack on a Python framework that manages release identity through git tags + CHANGELOG.md headers only — no VERSION file at the repo root. The release model is "accumulate in ## [Unreleased], periodic release cuts promote to a dated ## [X.Y.Z] header" — pretty close to standard Keep a Changelog flow.

/ship is excellent and I want every PR to go through it. Two hard-fail points block that today:

  1. Step 9 STOPs when .claude/skills/review/checklist.md is missing. The skill is intentionally strict on this; my project just didn't have a project-local checklist.
  2. Step 12 exit 1s on the regex ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ because there's no 4-digit VERSION file. The queue-aware workspace-ship feature (bin/gstack-next-version) is also pinned to 4-digit input.

This is consistent with gstack's documented model (workspace-aware ship, VERSION as monotonic ordered identifier, every PR is its own release) but doesn't fit a project that ships releases via tags + CHANGELOG promotion only.

What I proposed in my local fork

For my own unblock today I patched ship/SKILL.md.tmpl on a local branch with two strictly-additive guards:

Step 9 fallback (missing-checklist):

  • If .claude/skills/review/checklist.md is missing, fall back to an inline default checklist covering universal categories (SQL safety, LLM trust boundary, concurrency/atomicity, error handling, auth/secrets, public surface hygiene, dependency risk) and warn. Existing behavior (STOP on unreadable-but-present file) preserved.

Step 12 NO_VERSION_FILE mode:

  • Probe [ ! -f VERSION ] BEFORE the existing FRESH/ALREADY_BUMPED/DRIFT_STALE_PKG/DRIFT_UNEXPECTED state machine. If absent, AskUserQuestion: A) PR-level update (bullets to ## [Unreleased], no version bump, no header promotion) or B) release cut (operator provides new 3-digit semver, validated against the latest dated CHANGELOG header, ## [Unreleased] promoted to ## [X.Y.Z] - <today>). The CHANGELOG_VERSION extraction uses an awk state machine that skips fenced code blocks (caught by an adversarial review round — grep -m1 -E '^## \[[0-9]' was picking up example versions inside ``` blocks in the ### BREAKING callout demo).
  • No-VERSION + no-CHANGELOG → explicit STOP with actionable error.
  • bin/gstack-next-version is not called in NO_VERSION_FILE mode (it strictly requires 4-digit input).

Step 13 CHANGELOG resolver — mode dispatch:

  • Top-of-resolver branch on SHIP_MODE. PR-level: validate ## [Unreleased] exists and has bullets, STOP if not (operator owns CHANGELOG voice). Release-cut: promote ## [Unreleased] to dated 3-digit header. Otherwise: existing auto-generate flow for VERSION-file projects.

Step 19 PR title rule — mode-gated:

  • Default and release-cut mode keep the existing v$NEW_VERSION mandate + bin/gstack-pr-title-rewrite.sh self-check.
  • PR-level mode drops the version prefix (every PR in PR-level mode would otherwise carry the same v0.13.0 until the next release cut, misleading anyone scanning the PR list). Conventional Commit prefix is left alone.

Why a proposal, not a PR

This is a meaningful change to a load-bearing skill (ship is 3K+ lines, very tuned). Before opening a PR I want your read on:

  1. Is this the right shape? Detecting absence of a VERSION file feels like the lowest-friction signal — but you may prefer an explicit per-project opt-in via gstack-config set versioning_mode tag-based or similar. That's more explicit but adds setup friction.
  2. Should it route through gstack-next-version? I excluded the util in NO_VERSION_FILE mode since it requires 4-digit input. If you'd want queue-aware shipping to work for tag+CHANGELOG projects too, the util would need 3-digit support (and the queue-claim semantics for projects where every PR doesn't bump VERSION need defining).
  3. Should Step 9 fallback move to a .gstack-config opt-in? The current STOP-on-missing-checklist behavior is intentional discipline. Removing it without a knob may erode that discipline for projects that should have a checklist but don't.
  4. document-release subagent's depth-2 markdown heuristic doesn't reach docs/spec/*.md or non-markdown files. For my project I documented an "Operator Manual Surface Check" in a release-runbook to cover that gap manually — but a project-local "doc-sync-map" sidecar that the subagent reads would be cleaner. Out of scope for this proposal but flagging for context.

I'm happy to open a PR if the shape is roughly right. The whole local patch is on a feature branch I'd rebase on current main; ~80 lines of .tmpl changes + 9 lines in scripts/resolvers/utility.ts. bun test test/skill-validation.test.ts test/gen-skill-docs.test.ts passes 707/707 with the patch.

Context

Thanks for gstack. Happy to discuss or close if you'd prefer this stay project-local.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions