Skip to content

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

Closed

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.

Activity

  1. david00m commented on May 29, 2026

    @david00m

    +1 on making /ship's versioning model pluggable — I have a closely related need that I think argues for the same versioning_mode knob you floated, just a different mode.

    My setup: a repo with a 4-digit VERSION file + CHANGELOG.md (the standard gstack model), but I run 10–15 parallel feature branches at once (via Conductor) and want to land them through a GitHub merge queue with minimal hand-holding.

    The blocker is subtly different from yours. Your "PR-level" mode still appends bullets to ## [Unreleased] on every PR. For my workflow even that is a problem: any two PRs that both edit CHANGELOG.md (or VERSION) conflict on the same lines, so they can't be built/merged in parallel — and a merge queue can't auto-resolve textual conflicts, so it's unusable. I need PRs to touch neither VERSION nor CHANGELOG.md.

    The mode I need ("external" / "bot-owned"): /ship skips the VERSION bump and the CHANGELOG edit entirely; a post-merge automation on main (release-please / changesets / a small Action) owns both — computes the next version, writes the dated CHANGELOG entry, commits back to main. This is the standard pattern for repos that land many PRs through a queue: once PRs stop touching those shared lines, the queue "just works."

    So the design space is three modes, which collapse nicely into the gstack-config set versioning_mode … knob you proposed:

    1. per-pr (today's default) — 4-digit VERSION bump + CHANGELOG entry per PR.
    2. tag-based (your case) — no VERSION file; ## [Unreleased] accumulation + release-cut promotion.
    3. external (my case) — /ship touches neither file; a post-merge bot owns VERSION + CHANGELOG. Unlocks merge queues + high parallelism.

    What I verified on v1.51.0.0 before commenting: there's no --no-changelog/--no-version flag, no config key suppresses the bump, and explain_level: terse only compresses prose — so there's no opt-out today, consistent with your Step 12 finding.

    For the external mode the change is largely "gate the Step 12 VERSION state-machine and the Step 13 CHANGELOG resolver behind the mode" — and gstack is already merge-queue-aware (/land-and-deploy handles GitHub merge queues / required checks per the TODOS), so the downstream plumbing awareness is partly there. Happy to open a PR for the external mode if a unified versioning_mode is the direction you like.

    (Filing here rather than as a separate issue to keep the versioning-mode design in one place — glad to split it out if you'd prefer.)

  2. time-attack commented on Jul 15, 2026

    @time-attack
    Contributor

    @dep0we Platform-agnostic /ship versioning for projects without a VERSION file remains unresolved until the selected implementation lands.

  3. capy-ai commented on Oct 11, 2026

    @capy-ai
    Contributor

    Thanks for the detailed proposal. /ship now ships without a version change when a repo has no VERSION source (v1.91.19.0), and its review checklist comes from the gstack install rather than the project. A merge-queue-friendly versioning mode would be a new request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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