diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml
index 0bbb9a0..c8af951 100644
--- a/.github/workflows/gate.yml
+++ b/.github/workflows/gate.yml
@@ -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:
@@ -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
@@ -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:
@@ -84,3 +89,4 @@ jobs:
args: ${{ inputs.args }}
project: ${{ inputs.project }}
pr-comment: ${{ inputs.pr-comment }}
+ context-sync: ${{ inputs.context-sync }}
diff --git a/.github/workflows/up.yml b/.github/workflows/up.yml
index 337ff75..0f7ddf5 100644
--- a/.github/workflows/up.yml
+++ b/.github/workflows/up.yml
@@ -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
@@ -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
diff --git a/README.md b/README.md
index d99411b..8ff5489 100644
--- a/README.md
+++ b/README.md
@@ -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
@@ -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
TM exact matches"] --> AI["2 · translate
AI + terminology"] --> CK["3 · check
placeholders · terms · tags"]
+ MEM["1 · reuse
content memory exact matches"] --> AI["2 · translate
AI + terminology"] --> CK["3 · check
placeholders · terms · tags"]
end
U --> PASS
CK -->|every gate met| CV["up to date
PR with translations"]
CK -->|needs a person| PK["parked
the review queue"]
- PK --> RV["review & approve
committed under .kapi/state"]
+ PK --> RV["review & approve
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
@@ -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`: