Skip to content
Open
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
14 changes: 10 additions & 4 deletions .github/workflows/gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ name: kapi ship gate
# pull-requests: write
#
# Ordinary builds never fail on target-language drift; the gate is the
# explicit, opt-in enforcement point. A `reviewed` threshold reads the
# decisions `kapi commit` writes under .kapi/state/, so commit them in the PR
# or the runner sees no approvals.
# explicit, opt-in enforcement point. An `established` threshold counts the
# approvals recorded in the project's context, which lives in kapi's workspace
# outside git. A project that shares its context through a backend needs
# `context-sync: pull` (kapi 1.3 and later), or the runner sees no approvals.

on:
workflow_call:
Expand Down Expand Up @@ -43,6 +44,10 @@ on:
description: "Commits to fetch, 0 for all. A diff-scoped check (--diff-range, --diff-against) reads the commits it compares, so set 0 for one"
type: number
default: 1
context-sync:
description: "kapi-action's context-sync: pull runs kapi context pull before the check, so the gate reads the project's context from the backend its kapi.yaml declares (kapi 1.3 and later); false skips it"
type: string
default: "false"
server:
description: "Bowrain server URL (exported as BOWRAIN_SERVER_URL)"
type: string
Expand All @@ -57,7 +62,7 @@ on:
required: false
outputs:
gate:
description: "pass or fail"
description: "pass or fail; empty when the check did not run"
value: ${{ jobs.gate.outputs.gate }}

jobs:
Expand All @@ -84,3 +89,4 @@ jobs:
args: ${{ inputs.args }}
project: ${{ inputs.project }}
pr-comment: ${{ inputs.pr-comment }}
context-sync: ${{ inputs.context-sync }}
5 changes: 5 additions & 0 deletions .github/workflows/up.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ on:
description: "Fail the workflow when the run parks instead of delivering partial progress"
type: boolean
default: false
context-sync:
description: "kapi-action's context-sync: pull runs kapi context pull before kapi up, and true also runs kapi context push after it, through the backend the project's kapi.yaml declares (kapi 1.3 and later); false skips both"
type: string
default: "false"
plugins:
description: "Plugin refs for setup-kapi (registry names)"
type: string
Expand Down Expand Up @@ -106,6 +110,7 @@ jobs:
project: ${{ inputs.project }}
args: ${{ inputs.args }}
fail-on-parked: ${{ inputs.fail-on-parked }}
context-sync: ${{ inputs.context-sync }}

- name: Deliver as a pull request
id: deliver-pr
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# kapi-workflows

Reusable GitHub Actions workflows for [kapi](https://github.com/neokapi/neokapi) — the whole continuous-localization happy path as one `uses:` line each. They compose [`setup-kapi`](https://github.com/neokapi/setup-kapi) and [`kapi-action`](https://github.com/neokapi/kapi-action); use those directly when you need a custom job shape.
Reusable GitHub Actions workflows for [kapi](https://github.com/neokapi/neokapi), the format-aware content engine. With one `uses:` line, `up.yml` brings a project's content to the state its `kapi.yaml` recipe declares, translations included, and delivers the result as a pull request, and `gate.yml` fails pull requests that do not meet the project's content quality gates. They compose [`setup-kapi`](https://github.com/neokapi/setup-kapi) and [`kapi-action`](https://github.com/neokapi/kapi-action); use those directly when you need a custom job shape.

## `up.yml` — catch up on a schedule, deliver a PR

Expand All @@ -24,26 +24,26 @@ jobs:

Runs `kapi up` — the kapi loop — and opens a pull request with the produced translations and a kapi up report (outcome, passes, parked locales). A run that **parks** (work remains that needs a person) still delivers what it caught up; set `fail-on-parked: true` to block instead.

Inputs: `project`, `args`, `create-pull-request` (default `true`), `fail-on-parked`, `plugins` (default `bowrain`), `kapi-version`, `server`, `runs-on`. Secrets: `bowrain-auth-token`, `anthropic-api-key`, `deliver-token` (a PAT or App token when CI/deploys should react to the delivered translations — the default `GITHUB_TOKEN` triggers no workflows). Outputs: `outcome`, `passes`, `parked-locales`, `has-changes`, `pull-request-url`.
Inputs: `project`, `args`, `create-pull-request` (default `true`), `fail-on-parked`, `context-sync` (default `false`), `plugins` (default `bowrain`), `kapi-version`, `server`, `runs-on`. Secrets: `bowrain-auth-token`, `anthropic-api-key`, `deliver-token` (a PAT or App token when CI/deploys should react to the delivered translations; the default `GITHUB_TOKEN` triggers no workflows). Outputs: `outcome`, `passes`, `parked-locales`, `has-changes`, `pull-request-url`.

### What a run does

Each pass, for every language behind its ship gate: **reuse** exact translation-memory matches first (free), **translate** what remains with the configured AI provider plus the project's terminology, then **check** what was produced — placeholder integrity, inline tags, do-not-translate terms. A unit with a failing finding counts as *drafted*, not translated, so bad output can never lift a language over its gate. Passes repeat until every gate clears or nothing progresses.
`kapi up` settles the source first: the source checks run, and a block whose source is below the recipe's `defaults.translate_after` level is held rather than translated. Then, each pass, for every language behind its ship gate: **reuse** exact content-memory matches first (free), **translate** what remains with the configured AI provider plus the project's terms and voice, then **check** what was produced: placeholder integrity, inline tags, do-not-translate terms. A unit with a failing finding still counts as *translated*, and the finding holds its language out of shipping until it is fixed, so bad output never lifts a language over its gate. Passes repeat until every gate clears or nothing progresses.

```mermaid
flowchart LR
S[source changes] --> U[kapi up]
subgraph PASS ["each pass, per language behind its gate"]
TM["1 · reuse<br/>TM exact matches"] --> AI["2 · translate<br/>AI + terminology"] --> CK["3 · check<br/>placeholders · terms · tags"]
MEM["1 · reuse<br/>content memory exact matches"] --> AI["2 · translate<br/>AI + terminology"] --> CK["3 · check<br/>placeholders · terms · tags"]
end
U --> PASS
CK -->|every gate met| CV["up to date<br/>PR with translations"]
CK -->|needs a person| PK["parked<br/>the review queue"]
PK --> RV["review & approve<br/>committed under .kapi/state"]
PK --> RV["review & approve<br/>recorded in the project's context"]
RV -.->|next run sees it| U
```

Parked work is the review queue, not an error: a person reviews and approves it, `kapi commit` records the decision under `.kapi/state/` (or the connected server records it), and the `reviewed` coverage the ship gate measures goes up. The next run and the next gate see it.
Parked work is the review queue, not an error: a person reviews and approves it, and the approval is recorded in the project's context (with `kapi apply` or the Review page of Kapi Desktop), or the connected server records it. A machine takes a target unit from *draft* to *translated*; an approval takes it to *established* and raises the `established` coverage the ship gate measures. From kapi 1.3 the project's context (terms, voice profiles, content memory and recorded decisions) lives in kapi's workspace outside git, and a project shares it through the context backend its `kapi.yaml` declares, with `kapi context push` and `kapi context pull`. Set `context-sync: true` on `up.yml` so a run takes in what the team pushed and pushes what it recorded; the next run and the next gate then see the approval.

## `gate.yml` — fail PRs on unmet content quality gates

Expand All @@ -61,9 +61,9 @@ jobs:
pull-requests: write
```

Runs `kapi check --ship`: the project's bound gates (voice, terminology, rule-based checks) plus its ship and source coverage gates. An unmet gate exits `3`, fails the job with a distinct "gate unmet" annotation, and posts one sticky report comment on the PR. Ordinary builds never fail on target-language drift; the gate is the explicit, opt-in enforcement point.
Runs `kapi check --ship`: the project's bound gates (voice, terms and QA) plus its ship and source coverage gates. Each finding either fails its gate or only reports. An unmet gate exits `3`, fails the job with a distinct "gate unmet" annotation, and posts one sticky report comment on the PR. A check that did not run, because it examined no content or a checker could not show it is able to fail, exits `4` and fails the job with the cause. Ordinary builds never fail on target-language drift; the gate is the explicit, opt-in enforcement point. An `established` threshold counts the approvals in the project's context, so a project that shares its context through a backend sets `context-sync: pull`.

Inputs: `project`, `args` (default `--ship`), `plugins`, `kapi-version`, `pr-comment` (default `true`), `fetch-depth` (default `1`), `server`, `runs-on`. Output: `gate` (`pass`/`fail`).
Inputs: `project`, `args` (default `--ship`), `plugins`, `kapi-version`, `pr-comment` (default `true`), `fetch-depth` (default `1`), `context-sync` (default `false`), `server`, `runs-on`. Output: `gate` (`pass`/`fail`, empty when the check did not run).

A diff-scoped check reads the commits it compares, so pass `fetch-depth: 0` with `--diff-range` or `--diff-against`:

Expand Down
Loading