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`: