Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

shipright

"Ship it" means more than git commit && git push. This makes sure that survives contact with an agent.

A Codex and Claude Code skill, plus a Claude prompt hook. The skill defines shipping as a release gate — tests pass, docs match the change, then commit + push + tag. The Claude hook makes sure the skill actually fires there; Codex discovers the linked skill directly from its global skills directory.

The problem it solves

"Ship" is ambiguous, and the ambiguity is expensive. To most people "ship it" is two git commands. If that is not what you mean, a skill alone is not enough: skills are selected by relevance, and a request the model reads as "just commit and push" gets exactly that.

A git hook is the obvious fix and the wrong one, for three reasons:

  • It fires too late. Commit is the last step, so a commit hook triggers inside the thing it is supposed to gate — after the tests and docs should already have happened.
  • It cannot do the work. "Are the docs current?" is a judgement call. A shell script can detect and refuse; it cannot author. Its best case is a wall, not a workflow — and the bypass is one --no-verify away.
  • It does not fire on the actual failure. If "ship it" gets read as something smaller, no commit happens, so no git hook ever runs. The ambiguity is upstream of git.

So the trigger sits where the ambiguity is: on the word itself, at prompt submit.

How it works

piece what it does
skill/SKILL.md the workflow: gates, versioning rules, what stops a ship
hooks/invoke-ship-prompt.sh UserPromptSubmit hook — matches ship phrasing, injects a pointer to the skill
hooks/Invoke-ShipPrompt.ps1 same, for machines without bash
skill/agents/openai.yaml Codex-facing name, description, and invocation prompt
install.ps1 links the skill into both ~/.codex/skills/ and ~/.claude/skills/, and can wire the Claude hook
tests/Test-Hooks.ps1 runs every trigger phrase through both hooks and fails if they disagree
tests/Test-Skill.ps1 holds the frontmatter description, the skill body, and this README to the same story

The hook does not block anything. It injects context — "in this environment ship means the full workflow" — and lets the model act on it. Git hooks punish; prompt hooks instruct.

Install

git clone https://github.com/tohuw/shipright ~/repos/shipright
cd ~/repos/shipright
.\install.ps1 -Wire

Restart Codex so it refreshes its global skill catalog. For Claude Code, open /hooks once or restart so it reloads the config.

The skill is symlinked, not copied, into both agents, so edits in this repo take effect immediately and remain available to commit here. install.ps1 falls back to a directory junction where a symlink would need elevation. Codex uses $CODEX_HOME/skills when CODEX_HOME is set, otherwise ~/.codex/skills.

What shipping means here

  1. Tests pass. A failure stops the ship.
  2. Docs match the change. Drift is fixed as part of shipping.
  3. Commit, push, tag. CalVer (YYYY.MM.DD) unless the repo already uses SemVer, in which case keep SemVer. Never mix schemes.

The scheme test runs CalVer first, deliberately: 2026.08.15 is also three dot-separated numbers, so a SemVer-first check classifies every CalVer repo as SemVer and then reads the next day's tag as a patch bump. This repo shipped that bug in its own versioning rule and hit it on its own second release.

Two opinions worth knowing before adopting it:

Coverage gaps are a task inside the ship, not a wall in front of it. If anything in the change set has no test asserting it, the skill writes the missing tests rather than reporting and stopping — an untested change is not ready to ship, so closing the gap is shipping work. The question is asked about the change, not the file: a heavily tested file still takes unasserted changes like a prompt string, a config default, or a new flag nothing exercises.

The risk this has to dodge is real, though, so the skill prescribes a method rather than just an instruction. Tests written by reading the implementation assert what the code does rather than what it should, encoding current behaviour — bugs included — and then defending it. So: derive assertions from the spec or the issue rather than from the new code, prefer contract tests where two halves must agree and can be tested against each other, and mutation-check anything load-bearing by breaking it on purpose to confirm the new test fails. A test that passes under mutation is theater.

A half-ship is worse than no ship. If a gate fails, nothing is committed, tagged or pushed. Committed-but-untagged leaves a repo nobody can reason about later.

Per-repo overrides

Drop a SHIPPING.md at a repo root and the skill follows it instead. Shipping is not the same everywhere — a library ships a tag, an app ships a deploy, a docs site ships a build.

Tuning the trigger

The pattern lives in one line of each hook script. It matches ship, shipped, shipping it/this/that, cut a release, tag a release, push it out, and commit and push.

Bare shipping is deliberately excluded — it collides with shipping containers, costs and labels. \b keeps the rest off "relationship", "starship" and "shipment". commit and push is matched on purpose: that phrasing is the ambiguity, not a separate case.

The hook runs on every prompt, synchronously, so it is built to answer "no" fast: about 80ms via bash, 215ms via PowerShell. That is why bash is preferred where it exists.

Both hooks match against the prompt field only, never the raw event JSON. The event also carries cwd, transcript_path and session_id, so a whole-payload match fires on every prompt typed in a checkout under a directory named ship.

Changing the pattern

Change it in both scripts, then run the tests:

.\tests\Test-Hooks.ps1

Every phrase runs through both implementations and a disagreement between them is a failure, even where each answer looks defensible alone — two copies of one regex is one regex and one liability.

The table is weighted toward phrases that must stay quiet, because the two failures are not symmetric. A miss costs one untagged release. A false positive runs on every prompt, injecting release instructions into unrelated work until you stop trusting the hook. Add a case to the table before loosening the pattern.

About

Make 'ship it' mean the whole workflow: tests, docs, then commit+push+tag. An agent skill with prompt-trigger support.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages