Repository navigation
Proposal: platform-agnostic versioning for /ship (no-VERSION-file projects) #1474
Description
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:
- Step 9 STOPs when
.claude/skills/review/checklist.mdis missing. The skill is intentionally strict on this; my project just didn't have a project-local checklist. - Step 12
exit 1s on the regex^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$because there's no 4-digitVERSIONfile. 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.mdis 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>). TheCHANGELOG_VERSIONextraction 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### BREAKINGcallout demo). - No-VERSION + no-CHANGELOG → explicit STOP with actionable error.
bin/gstack-next-versionis 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_VERSIONmandate +bin/gstack-pr-title-rewrite.shself-check. - PR-level mode drops the version prefix (every PR in PR-level mode would otherwise carry the same
v0.13.0until 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:
- Is this the right shape? Detecting absence of a
VERSIONfile feels like the lowest-friction signal — but you may prefer an explicit per-project opt-in viagstack-config set versioning_mode tag-basedor similar. That's more explicit but adds setup friction. - 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). - Should Step 9 fallback move to a
.gstack-configopt-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. document-releasesubagent's depth-2 markdown heuristic doesn't reachdocs/spec/*.mdor 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
- Project: https://github.com/dep0we/atomic-agents-stack (Python AI agent framework, 928 tests, pre-1.0)
- Dogfood PR using the patched /ship end-to-end: feat(ship): project-local /ship adaptation — checklist + runbook (closes #155) dep0we/atomic-agents-stack#159
- Adversarial review caught the awk-vs-grep fenced-block regex bug pre-merge — that bug alone would have shipped wrong PR titles + accepted regressed version numbers, so this proposal is grateful for
codex_reviews:-required + Opus-subagent-as-fallback discipline being core to the skill.
Thanks for gstack. Happy to discuss or close if you'd prefer this stay project-local.
Activity
david00m commentedon May 29, 2026
+1 on making
/ship's versioning model pluggable — I have a closely related need that I think argues for the sameversioning_modeknob you floated, just a different mode.My setup: a repo with a 4-digit
VERSIONfile +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 editCHANGELOG.md(orVERSION) 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 neitherVERSIONnorCHANGELOG.md.The mode I need ("external" / "bot-owned"):
/shipskips the VERSION bump and the CHANGELOG edit entirely; a post-merge automation onmain(release-please / changesets / a small Action) owns both — computes the next version, writes the dated CHANGELOG entry, commits back tomain. 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:per-pr(today's default) — 4-digit VERSION bump + CHANGELOG entry per PR.tag-based(your case) — no VERSION file;## [Unreleased]accumulation + release-cut promotion.external(my case) —/shiptouches 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-versionflag, no config key suppresses the bump, andexplain_level: terseonly compresses prose — so there's no opt-out today, consistent with your Step 12 finding.For the
externalmode 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-deployhandles GitHub merge queues / required checks per the TODOS), so the downstream plumbing awareness is partly there. Happy to open a PR for theexternalmode if a unifiedversioning_modeis 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.)
time-attack commentedon Jul 15, 2026
@dep0we Platform-agnostic /ship versioning for projects without a VERSION file remains unresolved until the selected implementation lands.
capy-ai commentedon Oct 11, 2026
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.