Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# GitLab CI parity for workspace-aware ship.
# GitLab CI parity for explicit release version checks.
# Mirrors .github/workflows/version-gate.yml and pr-title-sync.yml.
# Projects that mirror to GitLab get the same protection as GitHub.

Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ Invoke them by name (e.g., `/office-hours`).

| Skill | What it does |
|-------|-------------|
| `/ship` | Run tests, review, push, open PR. Workspace-aware version queue. |
| `/ship` | Run tests, review, commit, push, and open a ready-for-review PR. |
| `/land-and-deploy` | Merge the PR, wait for CI and deploy, verify production health. |
| `/canary` | Post-deploy monitoring loop using the browse daemon. |
| `/landing-report` | Read-only dashboard for the workspace-aware ship queue. |
| `/landing-report` | Read-only dashboard for the explicit release version queue. |
| `/document-release` | Update all docs to match what you just shipped. |
| `/document-generate` | Generate Diataxis docs (tutorial / how-to / reference / explanation) from code. |
| `/setup-deploy` | One-time deploy config detection (Fly.io, Render, Vercel, etc.). |
Expand Down
20 changes: 12 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -664,11 +664,15 @@ If the user asks you to skip the move (e.g., "just leave it as a fork PR"),
respect that — eval CI will fail with empty-env auth, but check-freshness,
workflow-lint, and windows-tests will still pass on the fork PR.

## CHANGELOG + VERSION style
## Explicit release VERSION + CHANGELOG style

**Versioning invariant (workspace-aware ship).** VERSION is a monotonic ordered
Ordinary `/ship` runs are versionless: they never modify VERSION, package
manifests, lockfile versions, or CHANGELOG.md. The rules in this section apply
only when a user deliberately invokes an explicit release/version workflow.

**Versioning invariant (release workflow).** VERSION is a monotonic ordered
release identifier, not a strict semver commitment. The bump level
(major/minor/patch/micro) expresses intent at ship time. Queue-advancing past a
(major/minor/patch/micro) expresses intent at release time. Queue-advancing past a
claimed version within the same bump level is explicitly permitted — if branch A
claims v1.7.0.0 as a MINOR and branch B is also a MINOR, B lands at v1.8.0.0
(still a MINOR relative to main). Downstream consumers must NOT rely on
Expand Down Expand Up @@ -712,8 +716,8 @@ against the SCALE of your branch's work, not just whether main moved forward.
If main bumped MINOR and your branch is also a substantial change, you bump
MINOR again on top (e.g., main at v1.14.0.0, your branch lands v1.15.0.0).

**VERSION and CHANGELOG are branch-scoped.** Every feature branch that ships gets its
own version bump and CHANGELOG entry. The entry describes what THIS branch adds
**VERSION and CHANGELOG are release-scoped.** Every explicit release gets its own
version bump and CHANGELOG entry. The entry describes what THAT release adds,
not what was already on main.

**The CHANGELOG entry is the diff between main and the shipping branch — what users
Expand Down Expand Up @@ -742,8 +746,8 @@ ownership"), document it as a property, not as a fix. The shipped system is what
the user gets; the path to that system is invisible to them.

**When to write the CHANGELOG entry:**
- At `/ship` time (Step 13), not during development or mid-branch.
- The entry covers ALL commits on this branch vs the base branch.
- During an explicit release workflow, never during ordinary `/ship` or mid-branch development.
- The entry covers ALL commits included in that release vs its base.
- Never fold new work into an existing CHANGELOG entry from a prior version that
already landed on main. If main has v0.10.0.0 and your branch adds features,
bump to v0.10.1.0 with a new entry — don't edit the v0.10.0.0 entry.
Expand Down Expand Up @@ -805,7 +809,7 @@ If the diff between the base branch version and this version has no user-facing
(only merges, only CHANGELOG edits, only placeholder work), the honest entry is one
sentence: "Version bump for branch-ahead discipline. No user-facing changes yet." Stop
there. Do not pad. Do not explain the plan that will ship eventually. Do not narrate
the branch's history. When real work lands, the entry will replace this at /ship time.
the branch's history. When real work lands, the entry will replace this at release time.

### Release-summary format (every `## [X.Y.Z]` entry)

Expand Down
12 changes: 6 additions & 6 deletions bin/gstack-next-version
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#!/usr/bin/env bun
// gstack-next-version — host-aware VERSION allocator for /ship.
// gstack-next-version — host-aware VERSION allocator for explicit releases.
//
// Queries the PR queue (GitHub or GitLab), fetches each open PR's VERSION,
// scans configurable Conductor sibling worktrees, picks the next free version
// slot at the requested bump level, and emits the whole picture as JSON.
//
// Contract: util NEVER writes files or mutates state. Pure reader + reporter.
// /ship consumes the JSON and decides what to do.
// A release workflow consumes the JSON and decides what to do.
//
// Usage:
// gstack-next-version --base <branch> --bump <major|minor|patch|micro> \
Expand Down Expand Up @@ -455,7 +455,7 @@ function parseArgs(argv: string[]): { base: string; bump: Bump; current: string;

// Auto-detect: if --exclude-pr wasn't passed, check whether the current branch
// already has an open PR and exclude it by default. This prevents the self-
// reference bug where /ship's own PR inflates the queue on rerun.
// reference bug where an explicit release PR inflates the queue on rerun.
function autoDetectExcludePR(): number | null {
const r = runCommand("gh", ["pr", "view", "--json", "number", "-q", ".number"]);
if (!r.ok) return null;
Expand All @@ -466,8 +466,8 @@ function autoDetectExcludePR(): number | null {
// ── git-only fallback (#2545) ────────────────────────────────────────────
//
// When the host query fails this util used to return `offline:true` with an
// EMPTY claim set, and /ship's instruction was "fall back to local BUMP_LEVEL
// arithmetic". Local arithmetic cannot see a sibling's claim, so the fallback
// EMPTY claim set, and the release workflow fell back to local bump arithmetic.
// Local arithmetic cannot see a sibling's claim, so the fallback
// allocated a version another open PR already held.
//
// That is not hypothetical. On 2026-08-12 in a downstream repo, `gh pr list`
Expand Down Expand Up @@ -661,7 +661,7 @@ async function main() {
process.exit(2);
}
// The repo's own width governs everything downstream: a 3-digit repo must
// not be handed a 4-digit slot, or /ship writes a version the repo's tooling
// not be handed a 4-digit slot, or the release workflow writes a version the repo's tooling
// can't read back (#2501).
const width = versionWidth(baseVersion);
if (bumpWasCoerced(args.bump, width)) {
Expand Down
22 changes: 10 additions & 12 deletions bin/gstack-version-bump
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
#!/usr/bin/env bun
// gstack-version-bump — deterministic version-state classifier + writer for /ship.
// gstack-version-bump — deterministic version-state classifier + writer for explicit releases.
//
// Extracted from ship Step 12 prose (v2 plan T9, hybrid CLI extraction). The
// idempotency classification and the dual-write to VERSION + package.json are
// pure deterministic logic; running them as tested code removes the single
// worst /ship footgun — re-bumping an already-shipped branch — from prose the
// agent could skip or misread when the step lives in a lazy-loaded section.
// Originally extracted from ship prose (v2 plan T9, hybrid CLI extraction).
// The idempotency classification and the dual-write to VERSION + package.json
// remain reusable deterministic release tooling.
//
// What STAYS agent judgment (NOT here): the bump-LEVEL decision (micro/patch vs
// minor/major, which may AskUserQuestion on feature signals) and the queue
Expand Down Expand Up @@ -33,7 +31,7 @@
// Contract: classify NEVER writes. write/repair mutate VERSION + the manifest
// + npm lockfiles (package-lock.json / npm-shrinkwrap.json, when present)
// only. No git mutation, no network. Mirrors gstack-next-version's
// reader/writer split so /ship composes them.
// reader/writer split so release workflows can compose them.
//
// Manifest resolution (all three subcommands accept --package-json-path):
// --package-json-path <p> → .gstack/package-json-path → ./package.json
Expand All @@ -57,7 +55,7 @@ import { extractVersion, isJsonVersionPath, npmVersion, setVersionInJson } from
// 3- or 4-digit (#2501). gstack's own VERSION stays 4-digit MAJOR.MINOR.PATCH.
// MICRO and stays the source of truth, but a repo whose pinned version source
// is a package.json holds plain 3-digit semver, and rejecting it here meant
// /ship could not write a version at all in such a repo. See lib/version-source.ts.
// release workflows could not write a version at all in such a repo. See lib/version-source.ts.
const VERSION_RE = /^[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?$/;
const DEFAULT = "0.0.0.0";

Expand Down Expand Up @@ -187,7 +185,7 @@ function readPkgVersion(pkgPath: string): { exists: boolean; version: string } {
try {
parsed = JSON.parse(raw);
} catch {
fail(`${pkgPath} is not valid JSON. Fix the file before re-running /ship.`, 2);
fail(`${pkgPath} is not valid JSON. Fix the file before re-running the release workflow.`, 2);
}
const version = (parsed as { version?: unknown })?.version;
return { exists: true, version: typeof version === "string" ? version : "" };
Expand Down Expand Up @@ -281,7 +279,7 @@ function classifyState(
!pkgExists || !pkgVersion || pkgVersion === expectedPkg || pkgVersion === current;
if (current === base) {
// VERSION unchanged vs base. A diverging package.json means someone hand-edited
// package.json bypassing /ship — unsafe to guess which is authoritative.
// package.json bypassing the release workflow — unsafe to guess which is authoritative.
if (!pkgAgrees) return "DRIFT_UNEXPECTED";
return "FRESH";
}
Expand Down Expand Up @@ -313,7 +311,7 @@ function cmdClassify(args: string[], cwd: string): void {
// the version itself.
const expectedPkg = jsonSource ? current : npmVersion(current);
const state = classifyState(current, baseV, pkg.exists, pkg.version, expectedPkg);
// Surface version-file absence so callers (and /ship) can tell "version is
// Surface version-file absence so release callers can tell "version is
// genuinely 0.0.0.0" from "we made up 0.0.0.0 because the file is missing"
// (#2600). Without this, the DRIFT_STALE_PKG dispatch on a missing VERSION
// would feed repair a fabricated version that passes the shape check.
Expand Down Expand Up @@ -486,7 +484,7 @@ function cmdRepair(args: string[], cwd: string): void {
if (!VERSION_RE.test(current)) {
fail(
`VERSION file contents (${current}) do not match MAJOR.MINOR.PATCH[.MICRO]. ` +
"Refusing to propagate invalid semver into package.json. Fix VERSION, then re-run /ship.",
"Refusing to propagate invalid semver into package.json. Fix VERSION, then re-run the release workflow.",
2,
);
}
Expand Down
4 changes: 2 additions & 2 deletions docs/designs/FIX_1671_PROFILE_MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ Make the writer use the same file the reader does.

### Rollout

- One commit. PATCH version bump per CHANGELOG style guide.
- CHANGELOG entry written by `/ship`. User-facing voice: lead with what users experience now that they didn't before (welcome_back tier kicks in on second visit).
- One commit. Release metadata, if needed, is handled by the explicit release workflow.
- User-facing release-note voice: lead with what users experience now that they didn't before (welcome_back tier kicks in on second visit).

## Follow-up TODOs

Expand Down
4 changes: 2 additions & 2 deletions docs/designs/v2_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -582,13 +582,13 @@ No stale diagrams to fix.
### Architectural decisions locked in

- **D1 (manifest format):** `sections/manifest.json` is the structured per-heavyweight registry (JSON, machine-readable for gen-skill-docs CI checks). SKILL.md skeleton is markdown headers + imperative prose blocks ("STOP. If X, Read `sections/Y.md`"). Matches Anthropic's documented `references/` style. No invented DSL.
- **D2 (drift control):** `sections/*.md.tmpl` is the source of truth; `sections/*.md` is generated. gen-skill-docs walks `<skill>/sections/*.tmpl` and writes `<skill>/sections/*.md` using the same resolver pipeline as SKILL.md. Cost: ~30 LOC in `scripts/gen-skill-docs.ts`. Eliminates the drift class that `test/ship-version-sync.test.ts` already suffers from (TODOS:1120).
- **D2 (drift control):** `sections/*.md.tmpl` is the source of truth; `sections/*.md` is generated. gen-skill-docs walks `<skill>/sections/*.tmpl` and writes `<skill>/sections/*.md` using the same resolver pipeline as SKILL.md. Cost: ~30 LOC in `scripts/gen-skill-docs.ts`. Eliminates drift between generated ship sections and their sources.
- **D3 (CI cost cap):** `EVALS_BUDGET_HARD_CAP=$30` env var enforced by `test/skill-e2e-budget-regression.test.ts`; build fails if a single run exceeds. Section-loading tests (Phase B) use minimal-bash fixtures (~$0.30 each) because they assert STRUCTURAL behavior (was the right file Read?) not output quality.

### Adjacent TODOS surfaced (informational, not blocking)

- **TODOS:161** — planned "resolver injection at session start" for browser-skills (P2). Has architectural overlap with this plan's `appliesTo` predicate. Decision: keep separate for now — browser-skill resolver injection is runtime (session-start hostname matching); our `appliesTo` is build-time (gen-skill-docs.ts). Different lifecycles, different concerns. Revisit only if the browser-skills work needs the same predicate shape.
- **TODOS:1120** — `test/ship-version-sync.test.ts` reimplements ship/SKILL.md.tmpl Step 12 bash. D2 (sections/*.md.tmpl pipeline) is the structural fix. Phase B work obviates this TODO; mark as resolved when ship/ extraction lands.
- **TODOS:1120** — resolved by D2's `sections/*.md.tmpl` pipeline; generated ship sections no longer require hand-maintained duplicate tests.
- **TODOS:1136** — `git show` fallback in ship/SKILL.md.tmpl Step 12 line 409. Phase B touches this; bundle the `git rev-parse --verify` fix into the version-bump section extraction.

### Test plan artifact
Expand Down
4 changes: 2 additions & 2 deletions docs/skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Detailed guides for every gstack skill — philosophy, workflow, and examples.
| [`/context-save`](#context-save) | **Save State** | Save working context (git state, decisions, remaining work) so any future session can resume. |
| [`/context-restore`](#context-restore) | **Restore State** | Resume from a saved context, even across Conductor workspace handoffs. |
| [`/health`](#health) | **Code Quality Dashboard** | Wraps type checker, linter, tests, dead code detection. Computes a weighted 0-10 score; tracks trends over time. |
| [`/landing-report`](#landing-report) | **Ship Queue Dashboard** | Read-only snapshot of the workspace-aware ship queue. Which version slots are claimed, which sibling workspaces have WIP. |
| [`/landing-report`](#landing-report) | **Release Queue Dashboard** | Read-only snapshot of the explicit release queue. Which version slots are claimed, which sibling workspaces have WIP. |
| [`/benchmark-models`](#benchmark-models) | **Model Benchmark** | Side-by-side cross-model benchmark for skills (Claude vs GPT vs Gemini). Latency, tokens, cost, optional LLM-judged quality. |
| | | |
| **Multi-AI** | | |
Expand Down Expand Up @@ -645,7 +645,7 @@ Once I have decided what to build, nailed the technical plan, and run a serious

`/ship` is for the final mile. It is for a ready branch, not for deciding what to build.

This is where the model should stop behaving like a brainstorm partner and start behaving like a disciplined release engineer: sync with main, run the right tests, make sure the branch state is sane, update changelog or versioning if the repo expects it, push, and create or update the PR.
This is where the model should stop behaving like a brainstorm partner and start behaving like a disciplined release engineer: sync with main, run the right tests, make sure the branch state is sane, commit cleanly, push, and create or update a ready-for-review PR. Ordinary `/ship` runs leave release notes and versioning to an explicit release workflow.

### Test bootstrap

Expand Down
7 changes: 6 additions & 1 deletion document-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -855,12 +855,17 @@ You are running the `/document-release` workflow. This runs **after `/ship`** (c
exists or about to exist) but **before the PR merges**. Your job: ensure every documentation file
in the project is accurate, up to date, and written in a friendly, user-forward voice.

**Docs-only mode:** `ordinary-ship-docs-only` sets `DOCS_ONLY=true`: skip Steps
5/8 and Step 9 title sync, use the versionless docs commit, omit release rows
from health output, and never change `VERSION`, `CHANGELOG.md`, or manifest/
lockfile versions. Standalone runs retain explicit release options.

You are mostly automated. Make obvious factual updates directly. Stop and ask only for risky or
subjective decisions.

**Only stop for:**
- Risky/questionable doc changes (narrative, philosophy, security, removals, large rewrites)
- VERSION bump decision (if not already bumped)
- VERSION bump decision (explicit release mode only)
- New TODOS items to add
- Cross-doc contradictions that are narrative (not factual)

Expand Down
7 changes: 6 additions & 1 deletion document-release/SKILL.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,17 @@ You are running the `/document-release` workflow. This runs **after `/ship`** (c
exists or about to exist) but **before the PR merges**. Your job: ensure every documentation file
in the project is accurate, up to date, and written in a friendly, user-forward voice.

**Docs-only mode:** `ordinary-ship-docs-only` sets `DOCS_ONLY=true`: skip Steps
5/8 and Step 9 title sync, use the versionless docs commit, omit release rows
from health output, and never change `VERSION`, `CHANGELOG.md`, or manifest/
lockfile versions. Standalone runs retain explicit release options.

You are mostly automated. Make obvious factual updates directly. Stop and ask only for risky or
subjective decisions.

**Only stop for:**
- Risky/questionable doc changes (narrative, philosophy, security, removals, large rewrites)
- VERSION bump decision (if not already bumped)
- VERSION bump decision (explicit release mode only)
- New TODOS items to add
- Cross-doc contradictions that are narrative (not factual)

Expand Down
Loading