Skip to content
Back to skills

Ship

ASecurity

Executes a full release cycle for the riddl project. Follow each step in order. **STOP immediately** if any assertion fails and report the problem.

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 24, 2026
code-qualitygitdocumentation

Security analysis

A100/100

Scanned September 24, 2026

npx -y skills add ossuminc/riddl --skill ship --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ship?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ship
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ossuminc-ship/badge)](https://www.skillsdirectory.com/skills/ossuminc-ship)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# Ship Skill

Executes a full release cycle for the riddl project. Follow each
step in order. **STOP immediately** if any assertion fails and
report the problem.

## Arguments

The user should provide a version number (e.g., `1.10.1`). If
not provided:
1. Run `git tag --sort=-v:refname | head -5` to find the
   actual latest tag (not just reachable from current branch)
2. Run `git log --oneline <latest-tag>..HEAD` to see changes
3. Analyze per semver and **recommend** a version (don't ask
   the user to choose — present your recommendation and let
   them confirm or override)

## Pre-Flight Checks

1. **Always ship a FINAL release from `main`.** Ordinary work commits
   directly to `main`, so in the usual case there is nothing to bring
   forward and you are already where you need to be:
   ```
   git checkout main
   git pull origin main
   git branch --show-current
   ```
   **There is no `development` branch.** It was deleted (local and
   remote) on 2026-08-27, having been 0 commits ahead of `main`; the
   GitFlow pre-flight this step used to prescribe — fast-forwarding
   `main` up to `development` — was a no-op or contrary to policy for
   every release from 1.30.0 onward, and was skipped by hand each time.
   See `CLAUDE.md` § Branch Strategy.

   **When the release IS on a branch**, which is the case for a large
   change such as `release/2`, merge that branch into `main` and tag
   `main`. Do not tag the branch:
   ```
   git checkout main && git pull origin main
   git merge --no-ff release/2
   ```
   Release CANDIDATES are the documented exception and may be tagged on
   the release branch — an RC is a prerelease on GitHub, an `@rc` formula
   in the tap and an `rc` dist-tag on npm, so nothing resolves to it
   unless asked for by name. See the `/rc` skill.

2. Assert working tree is clean:
   ```
   git status --porcelain
   ```
   If dirty, list the uncommitted files and ask the user how
   to proceed. Scratch/test files that aren't part of the
   release should be removed or set aside, not committed.

3. **GITHUB_TOKEN handling**: Do NOT `unset GITHUB_TOKEN`
   globally — sbt needs it for GitHub Packages resolution.
   Only unset it immediately before `gh` commands:
   ```
   unset GITHUB_TOKEN && gh ...
   ```

4. Verify the version tag does not already exist:
   ```
   git tag -l <VERSION>
   ```

## Ship Steps

6. **Ensure the working tree is clean before tagging.**
   sbt-dynver derives the version from `git describe`;
   any dirty tree causes it to append a
   `<VERSION>-N-<hash>-<timestamp>` suffix instead of
   the clean `<VERSION>`. This suffix would propagate
   to BuildInfo, all published artifacts, and npm
   packages. Check:
   ```
   git status --porcelain
   ```
   If any files are modified or untracked, commit them
   now before proceeding.

7. Create an annotated git tag:
   ```
   git tag -a <VERSION> -m "Release <VERSION>"
   ```

8. Verify dynver resolves to the clean version:
   ```
   sbt 'show riddlc/version'
   ```
   The output must be exactly `<VERSION>` with no suffix.
   If it has a suffix, do NOT proceed — delete the tag
   (`git tag -d <VERSION>`), fix the issue, and re-tag.

9. Run the full test suite and publish all modules:
   ```
   sbt "; clean; tJVM; tJS; tNative; publish"
   ```
   **NEVER `sbt clean test publish`.** That was prescribed here until
   2026-08-27 and it is a gate that can skip most of the suite while
   reporting success. In sbt 2 bare `test` resolves to **`testQuick`**,
   which skips suites it judges unaffected — and **that judgement
   survives `clean`, because the action cache does**. It once left the
   JS row of a GREEN run executing 109 of 567 tests, with `languageJS`,
   `passesJS` and `testkitJS` never running at all. `tJVM`/`tJS`/`tNative`
   exist precisely to defeat this: they are `testOnly *`, which ignores
   incremental state. See the alias comments in `build.sbt`.

   **Count what ran.** One `Suites: completed N` line per module in each
   alias, and zero `No tests to run`. A short count means a module was
   skipped or the `;` chain aborted at a failure — either way, look.

   **For a MAJOR release, certify from a genuinely cold cache first**
   (Reid, 2026-08-27, shipping 2.0.0). `clean` removes `target/` but NOT
   the shared store at `~/.cache/sbt/v2` (or `~/Library/Caches/sbt/v2`),
   so results can still be replayed:
   ```
   sbt -batch shutdown                       # a warm server IGNORES the -D
   sbt --server -Dsbt.global.localcache=/tmp/sbt-verify-<ver> -batch "; clean; tJVM; tJS; tNative"
   ```
   The property applies only at **server boot**, so the shutdown is not
   optional. Afterwards the throwaway directory must EXIST and have GROWN
   (`du -sh`) — an empty one means the run was served from the shared cache
   and certified nothing.

   Because the tag is on HEAD and the tree is clean,
   BuildInfo and all published artifacts will carry the
   clean `<VERSION>`. Verify in the sbt output.
   **If tests fail, delete the tag** (`git tag -d
   <VERSION>`) — do NOT push a broken release.

10. Push commits and tag to origin:
    ```
    git push origin main <VERSION>
    ```

11. **Write detailed release notes** and create the GitHub
    release. Do NOT use `--generate-notes` — the auto-generated
    notes are just commit titles and are not suitable for a
    public repository.

    Instead, read all commits since the previous tag:
    ```
    git log --format="%H %s" <PREV_TAG>..<VERSION>
    ```
    Then read the diffs for any non-trivial commits to
    understand what actually changed. Write human-readable
    release notes in this format:

    ```markdown
    ## What's New

    ### Features
    - **Feature name** — Clear description of what was added
      and why it matters to users.

    ### Bug Fixes
    - **Area affected** — What was broken and how it's fixed.

    ### Improvements
    - **Area affected** — What changed and why it's better.

    ### Internal
    - Dependency upgrades, CI fixes, documentation updates,
      and other changes that don't affect end users directly.
    ```

    Omit any section that has no entries. Focus on what users
    and consumers of the library need to know. Use clear,
    complete sentences — not just commit message echo.

    Create the release:
    ```
    unset GITHUB_TOKEN && gh release create <VERSION> \
      --title "Release <VERSION>" --notes "$(cat <<'EOF'
    <release notes here>
    EOF
    )"
    ```
    This triggers the Release Artifacts workflow (native
    builds, Homebrew formula update) and the npm-publish
    workflow automatically.

## Post-Release Verification

12. Confirm the release exists:
    ```
    unset GITHUB_TOKEN && gh release view <VERSION>
    ```

13. Run `git status` to confirm the working tree is clean.
    **Known issue:** The `sbt clean` step in the build may
    trigger sbt-ossuminc's copyright header formatter, which
    updates files that still have stale headers (e.g.,
    `"Ossum, Inc."` → `"Ossum Inc."`). If `git status` shows
    modified files that are only copyright header changes:
    - Commit them: `git add -u && git commit -m "Fix copyright headers"`
    - Push: `git push origin main`
    These changes are harmless formatting fixes and should be
    committed straight to `main`.

14. **Delete the release branch, if the release came from one.**
    There is no merge-back step: `main` IS the working branch, so
    there is nowhere to merge the tag forward TO. This step used to
    say `git checkout development && git merge main`, which since
    1.30.0 has been either a no-op or contrary to policy.
    ```
    git branch -d <release-branch>
    git push origin --delete <release-branch>
    ```

15. Report a summary: tag, commit SHA, release URL, and any
    CI workflows triggered.

16. **Drop upgrade tasks in dependent projects.** Invoke
    `/ossuminc-skills:bump-consumers` with library `riddl` and the
    version just released. That skill owns the consumer list
    (`skills/bump-consumers/consumers.md`) and the task-file format —
    do **not** maintain a repo list here. riddl has more consumers
    than any other library (Scala via `project/Dependencies.scala`;
    npm via `@ossuminc/riddl-lib` in `package.json`), so let the skill
    apply its usual restraint: it asks before writing and proposes
    skipping patch releases rather than filing a task in every
    consumer for a bugfix. Requires the `ossuminc-skills` plugin
    installed (`claude plugin list | grep ossuminc-skills`).

## If Something Fails

- If tests fail in step 9: delete the local tag
  (`git tag -d <VERSION>`), fix, and restart from step 6.
  Do NOT push a broken tag.
- If dynver shows a suffix in step 8: delete the tag, fix
  the dirty tree, and re-tag.
- If tag push fails in step 10: check if tag exists
  remotely.
- If publish fails in step 9: check credentials and retry
  (tag is still local, safe to retry).
- If `gh release create` fails in step 11: the tag is
  already pushed, so the release can be created manually
  or retried.
- **Never force-push tags** without explicit user approval.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…