Ship gates & CI
Goal: treat content quality like any other test. A ship gate is the project's release bar: coverage thresholds over the translation lifecycle plus the bound quality gates (voice, terminology, rule-based checks, staleness). kapi check --ship evaluates all of it at once and exits non-zero when a gate is unmet, so a regression cannot merge. Ordinary builds never fail on target drift; a language that is behind is pending work, and you opt in to enforcement with --ship. The model is the kapi loop. You need a committed kapi project.
- Desktop
- Agent (Claude)
- CLI
The project view shows each locale's standing against its gate continuously: the same derivation check --ship enforces:
- Read the gate cells. Each locale row shows coverage per lifecycle rung (draft, translated, approved) and whether the scope clears its gate.
- Close the gap. Bring up to date produces what a machine can; a gate cell opens the Review queue for what needs a person.
- Ship when green. When every scope clears its gate, the CI check below passes too.
The coverage grid with per-locale ship-gate cells, one scope green, one pending with the unmet threshold named.
Ask for the standing, or for the fix:
Are we shippable in German? If not, get us there.
The assistant reads kapi status --json, runs kapi up for what a machine can close, goes through the review worklist with you for the rest, and confirms with kapi check --ship. With the Claude Code plugin installed, the Stop hook runs the gate automatically when Claude tries to finish; a failing gate keeps it working with the findings. See Use with Claude.
kapi check --ship runs the project's bound quality gates plus its ship/source coverage gates; with no file arguments it inspects the project's content across every target language:
kapi check --ship # the pre-release bar: exit 0 pass, 3 gate fail, 4 did not run
kapi check --ship --locale de # scope the target-side gates to one locale
kapi check --ship --json # structured result for a job summary
A gate that finds no content to check, or whose checks could not show they flag a known problem, did not run. It reports did_not_run with a did_not_run_cause and exits 4, even with --no-fail, so an empty scope never reads as a passing release.
The terminology gate runs wherever terms govern content in scope: terms bound by the project defaults, by a profile that a collection's channel resolves to, or by the term_rules: the recipe declares on a flow step or a tool preset. Its coverage counts content that neither the bound terms nor the recipe's rules answer for as not_governed, and a governed target that could not be read as not_checked. When none of the governed content could be read, the gate did not run.
In GitHub Actions, install the CLI with the companion setup-kapi action and run the gate:
name: Ship gate
on:
pull_request:
paths: ["content/**", "src/locales/**"]
jobs:
ship-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: neokapi/setup-kapi@v1
with:
version: "1.2.0"
# Reads the repository's context files into the runner's empty store.
- run: kapi store import
# Exits 3 (failing the job) when a voice, terminology, check,
# staleness or coverage gate is unmet, and 4 when a gate did not run.
- run: kapi check --ship
kapi finds the recipe by walking up from the working directory, so no project path is needed at the repo root.
A runner starts with an empty context store, because a project's voice profiles, terms and approvals live in a workspace on the machine that holds them (see the project store). A project with a context backend runs kapi context sync first, which is what lets a voice, terms or established gate pass on a runner; see Convergence in CI.
For a targeted, file-level gate on named files (no project), use plain kapi check. If a CI job also produces translations (kapi up in a workflow), give it an API key via a secret env var; a Claude subscription sign-in is bound to your user and its usage window, so CI always uses a key.
On GitLab, the same gate is one include: the check component runs on merge-request pipelines and on pushes to the default branch, fails the pipeline on exit 3 (a gate is unmet) or 4 (a gate did not run), and posts one threaded MR note with the findings:
include:
- component: gitlab.com/neokapi/kapi-components/check@0.2.0
The full pipeline picture (catch up on a schedule, deliver a PR/MR, gate merges, report the cost of a change) is in The kapi loop in CI.
File checks vs project gates
One command sets two bars:
kapi check <files> | kapi check --ship | |
|---|---|---|
| Gates | the files you name | the project |
| Measures | a checkset over their content; fails when a finding fails | coverage against the recipe's ship_gates: plus the bound quality gates, across every target language |
| Needs a project | no | yes |
| Use it | while content is being written | before it ships |
See Check content like tests for the file-level half.
Next
- The kapi loop: gates, ladders, and the derived-state model.
- Tier gates per market: different review bars per locale, one enforcement job.
- Review & approve: raising the approved coverage an
establishedthreshold measures. - Check content like tests: the file-level checkset and Report.