Gå til hovedinnhold

The kapi loop in CI

Source content changes on every merge, and everything derived from it — its translations, its brand and terminology conformance — falls behind. That drift is the normal state of a shipping project, not an error. Running the kapi loop in CI keeps every language caught up from the pipeline that produces the drift: close what a machine can close, deliver it as a reviewable pull request, and enforce the release bar on merges — content quality treated like any other test. This covers the source language too: the ship gate includes the source gates (brand, terminology) alongside translation coverage and QA.

changea merge or a schedule tickcatch upkapi up — memory first, then AI, then checkseach pass re-derivescoverage and repeatscaught up / parkeddelivera pull request with the run reportexit 3 blocks the mergegatekapi check --ship on merge requests

CI catches up and delivers; the gate — not drift — decides what blocks a merge.

Inside a pass, for every language behind its gate, the default flow does three things in order: reuse — exact translation-memory matches fill first, for free; translate — the configured AI provider fills what remains, with the project's terminology and brand context; check — deterministic checks run over what was produced (placeholder integrity, inline tags, do-not-translate terms), and a unit with a failing finding counts as drafted, not translated, so bad output can never lift a language over its gate. What a machine cannot decide parks for review; an approval is recorded in the committed state store, and the next run sees it.

Three commands carry the whole model, and they divide the work cleanly:

CommandRoleExit behavior
kapi statusObserve — per-locale coverage and gate standingAlways 0: behind is pending work, not an error
kapi upProduce — runs the kapi loop0 whether the run caught everything up or parked work for review; non-zero only when the run broke
kapi check --shipEnforce — the release bar0 pass, 3 gate unmet, 1 operational error

An ordinary build never fails because a target language is behind. The gate is the explicit, opt-in enforcement point — wire it into merge-request pipelines and a quality regression cannot merge, while day-to-day drift flows through review instead of breaking builds.

Source first: settle before you fan out

The loop is source-first: it settles the source and holds at the source gate before translating any language, because a term left unresolved or a sentence left off-brand in the source is paid for once per language and again on the fix. In CI this shows up two ways. kapi check --ship runs the project's source_gate alongside the target ship gates, so a merge whose source is off-brand or below its terminology bar fails the gate the same way a target-coverage regression does. And under source-first convergence, source that has not cleared its source_gate holds — reported as source_not_ready, routed to a source review — rather than spending on a machine translation of content that is about to change. Settling the source first is the cheap, honest outcome; see Source first for the model.

The surfaces

CIInstallRun
GitHub Actionssetup-kapi (CLI + plugins, cached)kapi-action, or the one-line reusable workflows
GitLab CI— (the components run on the image)kapi-components from the CI/CD Catalog
Anything container-basedghcr.io/neokapi/kapi (cosign-signed; a variant with the server-sync plugin preinstalled is published alongside)The CLI directly — the exit codes above are the contract

Catch up on a schedule, deliver a pull request

The produced translations land as a branch and a pull request carrying the run report — outcome, passes, parked locales. A run that parks (work remains that needs a person) still delivers what it did catch up; the parked locales are the review queue, not a failure.

.github/workflows/translations.yml
name: Translations
on:
schedule:
- cron: "0 6 * * 1-5"
workflow_dispatch:

jobs:
up:
uses: neokapi/kapi-workflows/.github/workflows/up.yml@v1
permissions:
contents: write
pull-requests: write
secrets:
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}

The reusable workflow composes setup-kapi and kapi-action; use the action directly (create-pull-request: "true") when the job needs a custom shape.

Gate merges on content quality

kapi check --ship runs the project's bound quality gates — brand, terminology, QA — plus its ship/source coverage gates, and exits 3 when any gate is unmet. Machine-produced translations fail quietly: a dropped placeholder, a competitor's term, wording that drifts off the approved vocabulary. The gate turns those from something a reader finds into something a pipeline finds.

.github/workflows/ship-gate.yml
name: Ship gate
on:
pull_request:
paths: ["content/**", "src/locales/**"]

jobs:
ship-gate:
uses: neokapi/kapi-workflows/.github/workflows/gate.yml@v1
permissions:
contents: read
pull-requests: write

An unmet gate fails the job with a distinct "gate unmet" annotation and posts one sticky report comment that re-runs update in place.

The Ship gates & CI recipe walks the gate end to end, including the committed state store that lets a reviewed threshold pass on a runner.

Report the cost of a change on its pull request

kapi up --plan dry-runs the loop: pending units, content-memory leverage, and a token estimate — no writes, no provider calls, no keys. Posted on the pull request that created the work, it answers "what does this change cost in translation?" before anyone merges:

- uses: neokapi/kapi-action@v1
with:
plan: "true"
pr-comment: "true"

This change leaves 42 unit(s) of pending translation work: 30 recoverable from content memory, 12 for AI (~450 tokens estimated).

Credentials and state on a runner

  • A producing job uses an API key — set a provider key (for example ANTHROPIC_API_KEY) as a CI secret. A subscription sign-in is bound to your user and its usage window; see Let kapi run Claude. In a server-connected project (a recipe with a server: block) the server holds the keys instead, and the job authenticates with a server token.

  • A gate-only job needs no keys at allcheck --ship and up --plan read the working tree.

  • Review decisions travel with the repo — a reviewed threshold reads the committed state store (.kapi-state.json); commit it in the PR or the runner sees no approvals.

  • The cache is rebuildable, and cacheable.kapi/cache (block store, extractions) is gitignored and rebuilt on a fresh runner. Restore it across runs to skip re-extraction:

    - uses: actions/cache@v5
    with:
    path: .kapi/cache
    key: kapi-cache-${{ hashFiles('kapi.yaml', 'src/locales/en/**') }}
    restore-keys: kapi-cache-

    Server-connected projects skip this — the project state lives on the server.

Works with Bowrain

Everything on this page runs the loop inside your own CI. A project whose recipe declares a server: block runs the same loop on a Bowrain server instead — kapi up pushes, the server catches every locale up against the workspace's shared memory and terminology, and the run streams back — with review handled in the Bowrain web app rather than in your checkout. See the Bowrain documentation.

Next