Šķîþ ţö ḿàîñ çöñţéñţ

kapi.ýàḿļ Þŕöĵéçţ Ƒîļé Ƒöŕḿàţ

Îḿþļéḿéñţàţîöñ ñöţéš ƒöŕ ţĥé kapi.yaml þŕöĵéçţ ƒîļé ƒöŕḿàţ. Šéé Ç-01 ƒöŕ ţĥé àŕçĥîţéçţüŕàļ đéçîšîöñ àñđ ţĥé þŕöĵéçţ ƒîļé ŕéƒéŕéñçé ƒöŕ éṽéŕý ķéý ŵîţĥ îţš ţýþé.

Šçĥéḿà

Ţĥé kapi.yaml ŕéçîþé îš à ÝÀḾĻ đöçüḿéñţ þàŕšéđ ƃý core/project.KapiProject:

type KapiProject struct {
Version string `yaml:"version"`
ID string `yaml:"id,omitempty"` // the stable identity; see Project identity
Name string `yaml:"name,omitempty"` // the label; free to change
Plugins map[string]PluginSpec `yaml:"plugins,omitempty"` // name → spec (scalar = version short form)
Defaults Defaults `yaml:"defaults,omitempty"` // project-wide defaults (locales live here)
Collections []Collection `yaml:"collections,omitempty"`
Preset string `yaml:"preset,omitempty"`
Flows map[string]*flow.StepsSpec `yaml:"flows,omitempty"`
Profiles map[string]Profile `yaml:"profiles,omitempty"` // profile name → governance (see The context space)

// Convergence gates (see the reference page, "Ship gates").
ShipGate gate.Gate `yaml:"ship_gate,omitempty"`
ShipGates []ShipGateRule `yaml:"ship_gates,omitempty"`
Gates map[string]gate.Gate `yaml:"gates,omitempty"`
VerifiedGate gate.Gate `yaml:"verified_gate,omitempty"`
VerifiedGates []ShipGateRule `yaml:"verified_gates,omitempty"`
SourceGate gate.Gate `yaml:"source_gate,omitempty"`

Requires RequiresMap `yaml:"requires,omitempty"` // plugin name → semver constraint
Extras map[string]yaml.Node `yaml:",inline"` // unknown keys (extensions)
}

// Defaults holds project-wide processing defaults, including locales.
type Defaults struct {
SourceLanguage model.LocaleID `yaml:"source_language,omitempty"`
TargetLanguages []model.LocaleID `yaml:"target_languages,omitempty"`
Flow string `yaml:"flow,omitempty"` // the flow `kapi up` runs
Materialize string `yaml:"materialize,omitempty"` // "manual" (default) or "on-converge"
Jobs int `yaml:"jobs,omitempty"` // target languages converged concurrently
SourceGate string `yaml:"source_gate,omitempty"` // authored | checked (default) | approved | none
LocaleFormat string `yaml:"locale_format,omitempty"`
Concurrency int `yaml:"concurrency,omitempty"`
ParallelBlocks int `yaml:"parallel_blocks,omitempty"`
Encoding string `yaml:"encoding,omitempty"`
Formats map[string]FormatDefaults `yaml:"formats,omitempty"`
Exclude []string `yaml:"exclude,omitempty"`
Merge MergeDefaults `yaml:"merge,omitempty"`
Memory MemoryDefaults `yaml:"memory,omitempty"`
Segmentation SegmentationDefaults `yaml:"segmentation,omitempty"`
Annotations AnnotationDefaults `yaml:"annotations,omitempty"`
Redaction *RedactionSpec `yaml:"redaction,omitempty"`
Comments CommentDefaults `yaml:"comments,omitempty"` // directives and channel for every declared comment
Voice *VoiceBinding `yaml:"voice,omitempty"`
Coordinates map[string]string `yaml:"coordinates,omitempty"` // the declared axes of the default point
TermsSource string `yaml:"terms_source,omitempty"`
MemorySource string `yaml:"memory_source,omitempty"`
Tools map[string]map[string]any `yaml:"tools,omitempty"` // per-tool presets
Locales map[string]LocaleDefaults `yaml:"locales,omitempty"` // per-target-language presets
Extras map[string]yaml.Node `yaml:",inline"`
}

// Collection is either a bare entry (path/format/target) or a named collection
// (name + content), and can carry its own source/target languages.
type Collection struct {
Name string `yaml:"name,omitempty"`
SourceLanguage model.LocaleID `yaml:"source_language,omitempty"`
TargetLanguages []model.LocaleID `yaml:"target_languages,omitempty"`
Content []ContentItem `yaml:"content,omitempty"`
Base string `yaml:"base,omitempty"` // the directory this collection lives in
Channel string `yaml:"channel,omitempty"` // `profile/channel` (named collections only)
Coordinates map[string]string `yaml:"coordinates,omitempty"` // declared axes; overlays defaults.coordinates per axis
SourceOnly bool `yaml:"source_only,omitempty"` // no target language; read and checked, never written
// Bare-entry fields (short form):
Path string `yaml:"path,omitempty"` // doublestar glob for source files
Format *FormatSpec `yaml:"format,omitempty"` // format ID; auto-detect per file if empty
Target string `yaml:"target,omitempty"` // output path template (tokens below)
Extras map[string]yaml.Node `yaml:",inline"`
}
// ContentItem additionally carries its own `base` (yaml:"base,omitempty"), the
// directory its matched paths are made relative to for target-token expansion,
// its own `channel`, a per-item `redaction`, and `comments` (ContentComments:
// `true`, or a mapping carrying `directives`, `channel` and `only`).

// Profile binds governance to one product and declares its channels.
type Profile struct {
Channels []Channel `yaml:"channels,omitempty"` // a slug, or {id, concept}
Voice *VoiceBinding `yaml:"voice,omitempty"` // same forms as defaults.voice
TermStore string `yaml:"termstore,omitempty"` // a standalone terms store, project-relative
Concept string `yaml:"concept,omitempty"` // display only; never resolved
ValidFrom string `yaml:"valid_from,omitempty"`
ValidTo string `yaml:"valid_to,omitempty"`
}

Ƒļöŵ đéƒîñîţîöñš ŕéüšé core/flow.StepsSpec àñđ core/flow.FlowStep (šéé ƒļöŵ-šţéþš-ƒöŕḿàţ).

Çöñţéñţ ḿöđéļ

Collections îš à ļîšţ öƒ Collection ṽàļüéš. Éàçĥ éñţŕý îš öñé öƒ ţŵö šĥàþéš, đîšţîñĝüîšĥéđ ƃý Collection.IsBareEntry():

  • Ƃàŕé éñţŕý: ĥàš à path àñđ ñö content. Ţĥé path, format, àñđ target ƒîéļđš àŕé þŕöḿöţéđ öñţö ţĥé çöļļéçţîöñ đîŕéçţļý. Üšé ţĥîš ƒöŕ à šîñĝļé ĝļöƃ ŵîţĥ ñö ĝŕöüþîñĝ.
  • Ñàḿéđ çöļļéçţîöñ: ĥàš à name àñđ à ñöñ-éḿþţý content ļîšţ öƒ ContentItem, àñđ ḿàý šéţ îţš öŵñ source_language / target_languages. Üšé ţĥîš ţö ĝŕöüþ ŕéļàţéđ þàţţéŕñš àñđ šçöþé ļàñĝüàĝéš þéŕ ĝŕöüþ.

À çöļļéçţîöñ ŵîţĥ source_only: true đéçļàŕéš ţĥàţ îţ ĥàš ñö ţàŕĝéţ ļàñĝüàĝé: à ŕüñ ŕéàđš îţ, çĥéçķš îţ, àñđ ŵŕîţéš ñöţĥîñĝ ƃàçķ. Ñàḿîñĝ ñö ţàŕĝéţ àļŕéàđý ḿàķéš à çöļļéçţîöñ šöüŕçé-öñļý; ţĥé ƒļàĝ îš ţĥé đéŕéñçé ƃéţŵééñ ḿéàñîñĝ îţ àñđ ƒöŕĝéţţîñĝ. Validate ŕéĵéçţš à çöļļéçţîöñ ţĥàţ šéţš îţ àñđ àļšö çàŕŕîéš à ţàŕĝéţ, îñ éîţĥéŕ šþéļļîñĝ (çöļļéçţîöñ öŕ îţéḿ, target: öŕ target_languages:).

Ŵĥéŕé à çöļļéçţîöñ ļîṽéš

Collection.Base îš ţĥé đîŕéçţöŕý ţĥé çöļļéçţîöñ ļîṽéš îñ. EffectiveItems ƒöļđš îţ îñ: éṽéŕý îţéḿ'š Path, Target àñđ öŵñ Base îš ĵöîñéđ öñţö îţ (JoinBase), šö éṽéŕý çöñšüḿéŕ đöŵñšţŕéàḿ šééš þŕöĵéçţ-ŕéļàţîṽé þàţĥš àñđ ñéṽéŕ ĥàš ţö ķñöŵ à ƃàšé ŵàš đéçļàŕéđ. Àñ àƃšöļüţé þàţĥ îš ļéƒţ àļöñé, šö ţĥé éšçàþé çĥéçķ đöŵñšţŕéàḿ šţîļļ šééš îţ; àñ éḿþţý öñé šţàýš éḿþţý.

Àñ îţéḿ ţĥàţ đéçļàŕéš ñö Base öƒ îţš öŵñ ķééþš ñöñé; ţĥé ţàŕĝéţ ţöķéñš ţĥéñ ŕéļàţîṽîžé àĝàîñšţ ţĥé ĵöîñéđ þàţţéŕñ'š öŵñ ƒîẋéđ þŕéƒîẋ. Ţĥàţ îš ŵĥàţ ḿàķéš base: à ļöçàţîöñ ŕàţĥéŕ ţĥàñ à šéçöñđ ŕéļàţîṽîžàţîöñ ŕööţ.

Ţĥé çöñţéẋţ šþàçé

Çöñţéñţ îš ŵŕîţţéñ ƒöŕ à þöîñţ îñ ţĥé çöñţéẋţ šþàçé. Ţŵö àẋéš àŕé šţŕüçţüŕàļ: ţĥé ÞŔÖĐÜÇŢ îţ ƃéļöñĝš ţö àñđ ţĥé ÇĤÀÑÑÉĻ îţ šĥîþš öñ. À ķéý üñđéŕ profiles: îš à þŕöđüçţ, ţĥé çĥàññéļš ţĥàţ þŕöƒîļé ļîšţš àŕé ţĥé çĥàññéļš ţĥàţ þŕöđüçţ šĥîþš öñ, àñđ à ñàḿéđ çöļļéçţîöñ ñàḿéš ţĥé þöîñţ îţš çöñţéñţ šîţš àţ ŵîţĥ öñé channel: ŕéƒéŕéñçé:

profiles:
northsea:
channels: [cli, docs]
voice: .kapi/voice.yaml # the project's default voice
acme:
channels:
- id: docs
concept: term:9a1c0f42b7 # display only; never resolved
- app
voice: .kapi/profiles/acme/voice.yaml # == the conventional location
termstore: .kapi/profiles/acme/terms.json # optional; the project's own store otherwise

collections:
- name: acme-app
channel: acme/app
- name: northsea-docs
channel: northsea/docs # both declare `docs`, so qualify it

Ţĥé ḿàþ ķéý üñđéŕ profiles: îš ţĥé þŕöƒîļé'š ñàḿé: ţĥé þŕöđüçţ-àẋîš ṽàļüé îţš çöļļéçţîöñš çàŕŕý, àñđ ţĥé đîŕéçţöŕý üñđéŕ .kapi/profiles/<name>/ ĥöļđîñĝ ţĥé ƒîļéš îţ öṽéŕŕîđéš. À þŕöƒîļé ţĥàţ ƃîñđš ñéîţĥéŕ à ṽöîçé ñöŕ à ṽöçàƃüļàŕý îš šţîļļ à þŕöƒîļé: ţĥàţ đîŕéçţöŕý îš ţĥé ƃîñđîñĝ, àñđ à þŕöĵéçţ ķééþîñĝ îţš öṽéŕŕîđéš ţĥéŕé šĥöüļđ ñöţ ĥàṽé ţö ŕéšţàţé éṽéŕý öñé öƒ ţĥéḿ îñ ţĥé ŕéçîþé.

Þŕöƒîļé ñàḿéš àñđ çĥàññéļš àŕé šļüĝš (^[a-z0-9][a-z0-9-]*$): šţàƃļé ḿàçĥîñé îđéñţîƒîéŕš, ñéṽéŕ ţŕàñšļàţéđ, çöḿþàŕàƃļé ƃýţé ƒöŕ ƃýţé. À þŕöƒîļé àñđ à çĥàññéļ ḿàý éàçĥ çàŕŕý à çöñçéþţ ƒöŕ đîšþļàý, ƃüţ ŕéšöļüţîöñ ñéṽéŕ ļööķš àţ îţ: çöñçéþţš àŕé đéšîĝñéđ ţö ƃé ŕéñàḿéđ àñđ đéþŕéçàţéđ àš ṽöçàƃüļàŕý îš ŕéṽîšéđ, àñđ ĝöṽéŕñàñçé ţĥàţ ḿöṽéđ ŵĥéñ šöḿéöñé éđîţéđ à ţéŕḿ ŵöüļđ ƃé ĝöṽéŕñàñçé ñöƃöđý çöüļđ ŕéļý öñ.

Ŕéšöļüţîöñ îš ƃý đéçļàŕàţîöñ, ñöţ ƃý ḿàţçĥîñĝ. KapiProject.ResolveChannel ŕéàđš öñé channel: ŕéƒéŕéñçé, àļŵàýš ţĥé ǫüàļîƒîéđ profile/channel. À ƃàŕé çĥàññéļ ñàḿé îš àñ éŕŕöŕ ţĥàţ šþéļļš öüţ ţĥé ǫüàļîƒîéđ ƒöŕḿ(š). Ţĥé ŕéšüļţ îš à ChannelRef{Profile, Channel}, ŵĥöšé žéŕö ṽàļüé îš ţĥé þŕöĵéçţ'š đéƒàüļţ þöîñţ.

Àƒţéŕ à þŕöƒîļé îš šéļéçţéđ, ţĥé çöļļéçţîöñ'š çĥàññéļ šéļéçţš ţĥé öṽéŕŕîđé îñšîđé ţĥàţ þŕöƒîļé'š ṽöîçé (profile.VoiceProfile.Channels, Ç-07), šö à ļàñđîñĝ-þàĝé ŕéĝîšţéŕ îš àüţĥöŕéđ öñçé ƃéšîđé ţĥé ṽöîçé îţ ṽàŕîéš ŕàţĥéŕ ţĥàñ đüþļîçàţéđ îñţö à ṽöîçé ƒîļé þéŕ þŕöđüçţ-àñđ-çĥàññéļ þàîŕ. À çĥàññéļ ţĥé þŕöƒîļé đéçļàŕéš ñö öṽéŕŕîđé ƒöŕ îš ñöţ àñ éŕŕöŕ: ţĥé ƃàšé ṽöîçé àþþļîéš, ŵĥîçĥ îš ţĥé ŕîĝĥţ àñšŵéŕ ƒöŕ à ṽöîçé ţĥàţ ŕéàđš ţĥé šàḿé éṽéŕýŵĥéŕé.

À ṽöîçé þŕöƒîļé çàñ đéçļàŕé šĥàŕéđ constraints ƃéšîđé tone àñđ style:

constraints:
- id: service/no-unsupported-assurance
version: 1
source: service-facts.md#assurances
statement: Do not promise a risk-free service.
kind: prohibited_pattern
regex: '(?i)\b(risk-free)\b'
- id: service/recording
version: 1
source: service-facts.md#recording
statement: Appointments are not recorded.
kind: guidance

Ţĥé çŕîţîçàļ þàţţéŕñ ŕéḿàîñš àþþļîçàƃļé ŵĥéñ à çĥàññéļ ŕéþļàçéš îţš šţýļé šéçţîöñ. Ţĥé ĝüîđàñçé šţàţéḿéñţ îš þàššéđ ţö ŵŕîţéŕš àñđ îš éẋþļîçîţļý öüţšîđé đéţéŕḿîñîšţîç šéḿàñţîç ṽéŕîƒîçàţîöñ. kapi context éẋþöšéš ŕéšöļṽéđ çöñšţŕàîñţ ŕéçöŕđš àñđ ţĥé ƒüļļ đéçļàŕéđ çööŕđîñàţé þöîñţ îñ ĴŠÖÑ. Ţĥé ţéẋţ ƒöŕḿ îñçļüđéš ţĥé šàḿé çöñšţŕàîñţ ĝüîđàñçé àñđ çööŕđîñàţéš.

scope çàñ ñàḿé éẋàçţ locale, channel àñđ persona ṽàļüéš; þöþüļàţéđ ṽàļüéš ḿüšţ àļļ ḿàţçĥ. Àñ exceptions éñţŕý ñééđš à ñöñéḿþţý šçöþé, à ŕéàšöñ, approved_by àñđ approval_ref. Àþþŕöṽàļ þŕöṽéñàñçé îñ à ļöçàļ ƒîļé îš àššéŕţéđ ƃý ţĥé àüţĥöŕ. Çüšţöḿ ŕéçîþé çööŕđîñàţéš àŕé šĥöŵñ îñ çöñţéẋţ àñšŵéŕš ƃüţ đö ñöţ àçţ àš çöñšţŕàîñţ þŕéđîçàţéš. Šéé Ç-07 ƒöŕ ŕéšöļüţîöñ, ṽàļîđàţîöñ àñđ šţöŕé-üþđàţé šéḿàñţîçš.

Ţĥé ŕéḿàîñîñĝ àẋéš àŕé đéçļàŕéđ. defaults.coordinates ñàḿéš ţĥé àẋéš ţĥé þŕöĵéçţ'š çöñţéñţ šîţš àţ üñļéšš à çöļļéçţîöñ šàýš öţĥéŕŵîšé, àñđ à çöļļéçţîöñ'š öŵñ coordinates: öṽéŕļàýš îţ þéŕ àẋîš. project.MergeCoordinates(defaults, derived, declared) îš ţĥé ŕüļé: ḿöšţ šþéçîƒîç ŵîñš, þéŕ àẋîš, àñđ àñ éḿþţý ṽàļüé ñéṽéŕ éŕàšéš à ƃŕöàđéŕ ļàýéŕ. product àñđ channel àŕé ŕéƒüšéđ üñđéŕ coordinates: ƃý project.DeclarableAxis, ƃéçàüšé ţĥéý àŕé đéŕîṽéđ ƒŕöḿ channel:. project.BrandAxis (brand) àñđ project.ModeAxis (mode, ŵîţĥ ţĥé Đîáţàẋîš ṽàļüéš tutorial, how-to, reference, explanation àš çöñṽéñţîöñš) àŕé ţĥé ţŵö đéçļàŕéđ àẋéš ţĥé ƒŕàḿéŵöŕķ šþéļļš; àñý öţĥéŕ ñàḿé îš ṽàļîđ (Ç-02).

KapiProject.ResolveGovernance(collection) ŕéšöļṽéš à çöļļéçţîöñ ñàḿé îñţö à ResolvedGovernance (çĥàññéļ, ṽöîçé ƃîñđîñĝ, TermStore, ţĥé þŕöƒîļé'š ñàḿé, àñđ ţĥé ŕéçîþé ķéý ţĥé ṽöîçé çàḿé ƒŕöḿ), ƒàļļîñĝ ƃàçķ ţö ţĥé þŕöĵéçţ đéƒàüļţš ƒöŕ àñ éḿþţý öŕ üñķñöŵñ çöļļéçţîöñ ñàḿé, àñđ ƒöŕ à çöļļéçţîöñ ţĥàţ ƃîñđš ñö çĥàññéļ; ItemForPath(relPath) ñàḿéš ţĥé çöñţéñţ îţéḿ ţĥàţ çļàîḿš à ƒîļé, ţĥé ƒîŕšţ îñ ŕéçîþé öŕđéŕ ŵĥöšé þàţţéŕñ ḿàţçĥéš îţ, àñđ CollectionForPath(relPath) ţĥé çöļļéçţîöñ ţĥàţ îţéḿ šîţš îñ. ProjectContext.ResolveContent àþþļîéš ţĥé šàḿé ŕüļé ŵĥéñ îţ éẋþàñđš ţĥé ŕéçîþé îñţö ƒîļéš, šö à ƒîļé ŕéšöļṽéš ţö öñé îţéḿ ŵĥîçĥéṽéŕ đîŕéçţîöñ ţĥé ǫüéšţîöñ îš àšķéđ ƒŕöḿ. Ţĥé ñàḿé ķééþš îţš đîšţàñçé ƒŕöḿ profile.ResolveContext, ŵĥîçĥ îš à đéŕéñţ ţĥîñĝ îñ à þàçķàĝé üšéđ àļöñĝšîđé ţĥîš öñé: ţĥé îñþüţ ţö þŕöƒîļé ŕéšöļüţîöñ, ñöţ ţĥé ŕéçîþé'š àñšŵéŕ.

Ţĥàţ îš ţĥé ŕéçîþé ĥàļƒ, àñđ îţ îš àñ àüţĥöŕîñĝ ĥàļƒ: ţĥé ṽöîçé îţ ñàḿéš îš ļöàđéđ ƃý ţĥé ĥöšţ àñđ ţĥéñ ĥàñđéđ ţö profile.ResolveProfileFromContext àš CollectionProfile, ţĥé çöļļéçţîöñ ţîéŕ öƒ ţĥé ƒŕàḿéŵöŕķ'š šîñĝļé þŕéçéđéñçé çĥàîñ (Ç-07), šö àñ éẋþļîçîţ þéŕ-çàļļ þŕöƒîļé šţîļļ öüţŕàñķš ţĥé ŕéçîþé àñđ à þŕöĵéçţ ĝöṽéŕñéđ ƒŕöḿ à ṽéñüé ŕàñķš îţš ƃîñđîñĝš îđéñţîçàļļý. Ţĥé þöîñţ'š çĥàññéļ ĝöéš îñ ƃéšîđé îţ àš CollectionConfig[PropertyChannel], àñđ ResolveProfile àþþļîéš ţĥé öṽéŕŕîđé.

ChannelRef.Coordinates() ŕéñđéŕš ţĥé šţŕüçţüŕàļ þöîñţ àš ţĥé ţŵö àẋéš ţĥàţ ţŕàṽéļ öñ ţĥé šýñç ŵîŕé, project.ProductAxis ("product") çàŕŕýîñĝ ţĥé þŕöƒîļé ñàḿé àñđ project.ChannelAxis ("channel") çàŕŕýîñĝ ţĥé çĥàññéļ, àñđ ţĥé đéƒàüļţ þöîñţ ŕéñđéŕš àš ñîļ. Ţĥé éñţŕý à þüšĥ çàŕŕîéš ƒöŕ à çöļļéçţîöñ îš ţĥé ḿéŕĝéđ þöîñţ (šţŕüçţüŕàļ þļüš đéçļàŕéđ àẋéš), îţš ṽöîçé ƃîñđîñĝ, àñđ îţš þŕéṽîéŵ ĥöšţ îƒ ţĥé ṽéñüé éẋţéñšîöñ đéçļàŕéš öñé, šö ƃöţĥ ṽéñüéš ŕéšöļṽé ţĥé šàḿé ṽöîçé ƒöŕ ţĥé šàḿé çöñţéñţ.

Ŵĥàţ đöéš ñöţ çŕöšš îš à þŕöƒîļé'š termstore:. Ţĥàţ îš à þàţĥ îñţö ţĥé ļöçàļ þŕöĵéçţ, àñđ à þàţĥ ḿéàñš ñöţĥîñĝ ţö à ṽéñüé ţĥàţ ĝöṽéŕñš ţéŕḿîñöļöĝý ƒŕöḿ à šĥàŕéđ ṽöçàƃüļàŕý. À ŕéçîþé ţĥàţ ƃîñđš à ţéŕḿš šţöŕé þéŕ þŕöƒîļé (KapiProject.BindsTermsByProfile) àñđ àļšö ƃîñđš à ṽéñüé îš ŵàŕñéđ àţ ŕüñ ţîḿé (host.WarnUnsyncedCoordinates, çàļļéđ ƃý kapi run, kapi up àñđ RunFlowAllLocales) ţĥàţ ţĥé ƃîñđîñĝ àþþļîéš ţö ļöçàļ ŕüñš öñļý. Ţĥé ŕüñ þŕöçééđš; ţĥîš îš à çàṽéàţ, ñöţ à ƒàüļţ.

Öñé ƒüñçţîöñ ŕéšöļṽéš ĝöṽéŕñàñçé ƒöŕ éṽéŕý šüŕƒàçé: KapiProject.ResolveGovernanceFor(GovernancePoint{Profile, Collection, Path, Comments, At}). Îţ ŵàļķš ţĥé đéçļàŕéđ ƃîñđîñĝš ƒîñéšţ-ƒîŕšţ (à çöñţéñţ îţéḿ'š öŵñ channel:, ţĥéñ îţš çöļļéçţîöñ'š, ţĥéñ ţĥé þŕöĵéçţ đéƒàüļţ), šķîþþîñĝ àñý ŵĥöšé þŕöƒîļé îš öüţšîđé îţš ṽàļîđîţý ŵîñđöŵ àţ At, àñđ ŕéçöŕđš ţĥé šķîþ öñ ResolvedGovernance.Fallback šö ţĥé çàļļéŕ çàñ ŕéþöŕţ îţ. À þöîñţ ŵîţĥ Comments šéţ ñàḿéš ţĥé çöḿḿéñţš îñ ţĥé ƒîļé àţ Path, àñđ îţš ŵàļķ šţàŕţš ŵîţĥ ţŵö ŕüñĝš öƒ îţš öŵñ: ţĥé çļàîḿîñĝ îţéḿ'š comments.channel, ţĥéñ defaults.comments.channel. ResolveGovernance àñđ ResolveGovernanceForPath àŕé ţĥé àš-đéçļàŕéđ ṽîéŵš öṽéŕ ţĥé šàḿé ŵàļķ (à žéŕö At àþþļîéš ñö ŵîñđöŵ); ResolveGovernanceAt îš ţĥé àš-öƒ ṽîéŵ.

À ŕüñ ŕéšöļṽéš þéŕ ƑÎĻÉ àñđ éẋéçüţéš öñçé þéŕ đîšţîñçţ ŕéšöļüţîöñ: groupInputsByBinding (ĥöšţ) þàŕţîţîöñš ţĥé îñþüţ šéţ ţĥŕöüĝĥ ţĥàţ ƒüñçţîöñ, àñđ éàçĥ ĝŕöüþ ĝéţš îţš öŵñ ƃîñđîñĝš àñđ îţš öŵñ ţööļ çĥàîñ. Ţĥé çĥàîñ îš ƃüîļţ ƃéƒöŕé àñý çöñţéñţ îš šééñ, šö ţĥé þàŕţîţîöñ îš ŵĥàţ ḿàķéš þéŕ-ƒîļé ĝöṽéŕñàñçé þöššîƃļé. Ĝŕöüþîñĝ ķéýš öñ ŵĥàţ ţĥé þöîñţš ŕéšöļṽé ţö, ñöţ öñ ţĥé þöîñţš ţĥéḿšéļṽéš, šö ţŵö çöļļéçţîöñš ĝöṽéŕñéđ ƃý öñé þŕöƒîļé šĥàŕé à ĝŕöüþ, àñđ à ŕéçîþé ŵĥéŕé ñöţĥîñĝ ƃîñđš à çĥàññéļ þŕöđüçéš éẋàçţļý öñé ĝŕöüþ: ţĥé šîñĝļé, üñšþļîţ ŕüñ.

Ţĥé îñšţàñţ îš ƒîẋéđ öñçé þéŕ ŕüñ (App.GovernanceInstant, šĥàŕéđ ƃý éṽéŕý çöñṽéŕĝé ŵöŕķéŕ), šö à ļöñĝ þàšš çàññöţ çŕöšš à ṽàļîđîţý ƃöüñđàŕý ĥàļƒŵàý ţĥŕöüĝĥ. Ţĥé ƒîŕšţ ƒàļļ-ţĥŕöüĝĥ þéŕ ŕüñ îš þŕîñţéđ öñ šţđéŕŕ àš governance: profile "x" expired <date>; governing with …, đéđüþļîçàţéđ ƃý App.NoteGovernance; kapi context search çàŕŕîéš ţĥé šàḿé šéñţéñçé îñ îţš ŕéšüļţ ñöţéš.

Éṽéŕý ƒàîļüŕé îš çàüĝĥţ àţ ļöàđ, ƃéçàüšé à šîļéñţ ƒàļļ-ƃàçķ ŵöüļđ ţŕàñšļàţé ţĥàţ çöñţéñţ îñ à þļàüšîƃļé-ļööķîñĝ ŵŕöñĝ ṽöîçé: à ñöñ-šļüĝ þŕöƒîļé ñàḿé öŕ çĥàññéļ, à çĥàññéļ đéçļàŕéđ ţŵîçé ƃý öñé þŕöƒîļé, à þŕöƒîļé ṽöîçé ƃîñđîñĝ ţĥàţ ñàḿéš ñö šöüŕçé öŕ ḿöŕé ţĥàñ öñé, à ḿàļƒöŕḿéđ çöñçéþţ ŕéƒéŕéñçé, à çĥàññéļ ŕéƒéŕéñçé ñàḿîñĝ àñ üñđéçļàŕéđ þŕöƒîļé öŕ çĥàññéļ, àñđ àñý ƃàŕé (üñǫüàļîƒîéđ) ŕéƒéŕéñçé. Ƃàŕé éñţŕîéš çàññöţ çàŕŕý à çĥàññéļ àţ àļļ: ŕéšöļüţîöñ îš ƃý çöļļéçţîöñ ñàḿé, šö à þöîñţ öñ àñ üññàḿéđ éñţŕý çöüļđ ñéṽéŕ ƃé ŕéàđ.

Ţĥé ŕéçîþé'š çööŕđîñàţé šüŕƒàçé îš ŵŕîţàƃļé ţĥŕöüĝĥ kapi apply ŵîţĥ kind: "recipe": defaults.coordinates.<axis> (öñé àẋîš þéŕ éñţŕý; àñ éḿþţý ṽàļüé ŵîţĥđŕàŵš îţ) àñđ collections.<name>.channel ĝö ţĥŕöüĝĥ project.SetField, ŵĥîçĥ àþþļîéš ţĥé šàḿé ŕéƒüšàļš àñđ þŕéšéŕṽéš ţĥé ŕéçîþé'š ƒöŕḿàţţîñĝ.

KapiProject.IterateContent ŵàļķš ƃöţĥ šĥàþéš üñîƒöŕḿļý, ýîéļđîñĝ éàçĥ ContentItem, ƃàšé àļŕéàđý ƒöļđéđ îñ, þàîŕéđ ŵîţĥ îţš þàŕéñţ çöļļéçţîöñ šö çàļļéŕš çàñ ŕéšöļṽé ƒàļļ-ţĥŕöüĝĥ ƒîéļđš. Ļàñĝüàĝé ŕéšöļüţîöñ ƒàļļš ţĥŕöüĝĥ îţéḿ → çöļļéçţîöñ → þŕöĵéçţ đéƒàüļţš ṽîà ContentItem.ResolvedSourceLanguage / ResolvedTargetLanguages. À ƃàŕé éñţŕý'š þŕöḿöţéđ ƒîéļđš àŕé ŵŕàþþéđ àš à šîñĝļé-îţéḿ šļîçé ƃý Collection.EffectiveItems, çàŕŕýîñĝ îţš Extras ţĥŕöüĝĥ šö þéŕ-îţéḿ éẋţéñšîöñ ƒîéļđš šüŕṽîṽé.

Đéƒàüļţš-šçöþéđ šéţţîñĝš

Defaults ĥöļđš þŕöĵéçţ-ŵîđé þŕöçéššîñĝ šéţţîñĝš ţĥàţ îñđîṽîđüàļ çöñţéñţ îţéḿš çàñ öṽéŕŕîđé. Ƃéýöñđ ļöçàļéš àñđ ţĥé þàŕàļļéļîšḿ/éñçöđîñĝ ķñöƃš šĥöŵñ àƃöṽé:

  • flow (šţŕîñĝ): ţĥé ƒļöŵ kapi up ŕüñš, à ƃüîļţ-îñ ñàḿé öŕ à ķéý îñ flows:. Éḿþţý ḿéàñš kapi run ŕéǫüîŕéš àñ éẋþļîçîţ ƒļöŵ.
  • materialize (manual | on-converge): ŵĥéţĥéŕ ţĥé çöñṽéŕĝéñçé ļööþ öŵñš đéļîṽéŕý öƒ ţĥé ţàŕĝéţ-ļàñĝüàĝé ƒîļéš. manual, ţĥé đéƒàüļţ, ļéàṽéš đéļîṽéŕý ţö kapi merge öŕ kapi up --materialize; on-converge ŵŕîţéš à ļöçàļé'š ƒîļéš öñļý ŵĥéñ îţš ĝàţéđ šçöþéš àŕé àļļ šĥîþþàƃļé.
  • jobs (îñţ): ĥöŵ ḿàñý ţàŕĝéţ ļàñĝüàĝéš öñé kapi up þàšš çöñṽéŕĝéš çöñçüŕŕéñţļý; up --jobs öṽéŕŕîđéš þéŕ ŕüñ.
  • source_gate (authored | checked | approved | none): ţĥé šöüŕçé šţàţüš à ƃļöçķ ḿüšţ ŕéàçĥ ƃéƒöŕé îţš ţŕàñšļàţîöñš àŕé þŕöđüçéđ; checked îš ţĥé đéƒàüļţ àþþļîéđ ŵĥéñ üñšéţ.
  • merge (MergeDefaults.ConflictPolicy): ĥöŵ kapi merge ŕéšöļṽéš à ţŕàñšļàţöŕ'š ţàŕĝéţ àĝàîñšţ àñ éẋîšţîñĝ öñ-đîšķ ţàŕĝéţ öŕ çöñţéñţ-ḿéḿöŕý éñţŕý (translator-wins đéƒàüļţ, existing-wins, newest-wins). Šéé Ḿ-01.
  • memory (MemoryDefaults): ţĥé þŕöĵéçţ'š çöñţéñţ ḿéḿöŕý: fuzzy_threshold, ţĥé þŕé-ƒîļļ çüţöƒƒ öñ kapi extract (đéƒàüļţ DefaultFuzzyThreshold = 75).
  • segmentation (SegmentationDefaults): öþţ-îñ ŠŔẊ šéñţéñçé šéĝḿéñţàţîöñ öṽéŕļàý öñ éẋţŕàçţ (source, öþţîöñàļ srx ŕüļéš ƒîļé).
  • annotations (AnnotationDefaults): ŵĥîçĥ öƒ à ƃļöçķ'š šţàñđ-öƒƒ àññöţàţîöñš à ŵŕîţéŕ đŕàŵš îñţö ţĥé đöçüḿéñţ àš îñļîñé ḿàŕķš; žéŕö ļéàṽéš éàçĥ ƒöŕḿàţ'š öŵñ đéçļàŕàţîöñ šţàñđîñĝ.
  • redaction (*RedactionSpec): ŕéþļàçé šéñšîţîṽé çöñţéñţ ŵîţĥ þŕöţéçţéđ þļàçéĥöļđéŕš ƃéƒöŕé þŕöçéššîñĝ àñđ ŕéšţöŕé îţ àƒţéŕŵàŕđš. Öṽéŕŕîđàƃļé þéŕ ContentItem.Redaction.
  • voice (*VoiceBinding): ƃîñđ à ṽöîçé þŕöƒîļé (öñé öƒ profile_file, profile, öŕ pack, öŕ à ƃàŕé þàţĥ) àš šţàñđîñĝ þŕöĵéçţ çöñţéẋţ.
  • coordinates (ḿàþ): ţĥé đéçļàŕéđ àẋéš öƒ ţĥé þŕöĵéçţ'š đéƒàüļţ þöîñţ (šéé àƃöṽé).
  • tools (ḿàþ öƒ ţööļ ñàḿé ţö çöñƒîĝ): þŕöĵéçţ-ļéṽéļ ţööļ þŕéšéţš, àþþļîéđ ŵĥéŕéṽéŕ ţĥé ţööļ ŕüñš îñ à þŕöĵéçţ ƒļöŵ; à ƒļöŵ šţéþ'š öŵñ çöñƒîĝ öṽéŕŕîđéš þéŕ ķéý.
  • locales (ḿàþ öƒ ļöçàļé ţö {tools}): þéŕ-ţàŕĝéţ-ļàñĝüàĝé þŕéšéţš ţĥàţ ḿéŕĝé öñ ţöþ öƒ tools àñđ üñđéŕ à šţéþ'š öŵñ çöñƒîĝ.
  • terms_source / memory_source (šţŕîñĝ): çöḿḿîţţéđ, ĝîţ-ţŕàçķéđ ñàţîṽé šöüŕçé ƃüñđļéš (.terms.json / .memory.json) ţĥé þŕöĵéçţ'š ţéŕḿš šţöŕé àñđ çöñţéñţ ḿéḿöŕý àŕé îñđéẋéđ ƒŕöḿ. kapi apply éđîţš ţĥé šöüŕçé àñđ ŕéîñđéẋéš îţ, šö ţĥé šöüŕçé îš ŵŕîţţéñ ƃý éẋàçţļý öñé þàţĥ àñđ git diff îš ţĥé ŕéṽîéŵ šüŕƒàçé. Ƃöţĥ ķéýš ƃîñđ àñý þàţĥ; ţĥé çöñṽéñţîöñàļ ĥöḿéš àŕé îñšîđé ţĥé çöḿḿîţţéđ çöñţéẋţ ĝŕàþĥ. terms_source ļéƒţ üñšéţ ƒàļļš ƃàçķ ţö <root>/.kapi/terms.json, ţĥéñ <root>/terms.json; memory_source ĥàš ñö šüçĥ ƒàļļƃàçķ, ƃéçàüšé à þŕöĵéçţ ĥàš öñé ţéŕḿš šöüŕçé ƃüţ ḿàñý ḿéḿöŕý ƃüñđļéš (öñé þéŕ çöñţéñţ šüŕƒàçé), ļéàṽîñĝ ñöţĥîñĝ šîñĝļé ƒöŕ à çöñṽéñţîöñ ţö ñàḿé.

Ţĥé þŕöĵéçţ šţöŕé

Ţĥé ŕéçîþé ƃîñđš šöüŕçéš; ţĥé šöüŕçéš àŕé ţĥé ţŕüţĥ. À þŕöĵéçţ ķééþš öñé ļöçàļ đàţàƃàšé, .kapi/work/store.db: à đéŕîṽéđ îñđéẋ öṽéŕ ţĥé çöḿḿîţţéđ šöüŕçéš (terms_source, memory_source, ţĥé ṽöîçé þŕöƒîļéš), ţĥé üñîţ-šţàţé ŕéçöŕđ üñđéŕ .kapi/state/, àñđ ţĥé çöñţéñţ ƒîļéš ţĥéḿšéļṽéš, þļüš ţĥé ŵöŕķîñĝ šéţ öƒ üñîţ šţàţé šţàĝéđ šîñçé ţĥé ļàšţ kapi commit. Éṽéŕý šüƃšýšţéḿ'š ţàƃļéš ļîṽé îñ ţĥàţ öñé ƒîļé: ƃļöçķ çàçĥé, ţéŕḿš šţöŕé, çöñţéñţ ḿéḿöŕý, ṽöîçé šţöŕé, ŵöŕķîñĝ šéţ, àñđ ţĥé þŕöþéŕţý ĝŕàþĥ. Šéé Ç-03 ƒöŕ ţĥé šţöŕé'š šĥàþé àñđ îţš ŕéƃüîļđ ĝüàŕàñţééš.

Éẋţéñšîöñš àñđ ţĥé ṽéñüé

Ţĥé ƒŕàḿéŵöŕķ ķñöŵš ñöţĥîñĝ àƃöüţ à þļüĝîñ'š ķéýš. Üñķñöŵñ ÝÀḾĻ ķéýš ļàñđ îñ Extras map[string]yaml.Node (ŵîţĥ yaml:",inline") öñ KapiProject, Defaults, Collection, àñđ ContentItem. À þļüĝîñ đéçöđéš îţš öŵñ ţýþéđ šçĥéḿà ƒŕöḿ ţĥéšé ḿàþš ṽîà GetExtra àñđ ŕé-éñçöđéš öñ SetExtra; ŕöüñđ-ţŕîþþîñĝ à ŕéçîþé ţĥŕöüĝĥ ţĥé ƒŕàḿéŵöŕķ àļöñé þŕéšéŕṽéš ţĥé ķéýš ṽéŕƃàţîḿ.

À ŕéçîþé ŵîţĥ ñö šüçĥ éẋţéñšîöñ îš à þüŕé ļöçàļ þŕöĵéçţ. Ţĥé kapi ÇĻÎ ţöļéŕàţéš üñķñöŵñ ƃļöçķš ƃüţ îĝñöŕéš ţĥéḿ; ţĥé öŵñîñĝ þļüĝîñ đéçöđéš ţĥéḿ ƒŕöḿ Extras. Àñ üñķñöŵñ ķéý ţĥàţ îš öñé éđîţ àŵàý ƒŕöḿ à ķñöŵñ ƒîéļđ öƒ ţĥé šàḿé šţŕüçţ îš ŕéþöŕţéđ ƃý KapiProject.KeyWarnings ŵîţĥ ţĥé ƒîéļđ îţ ŕéšéḿƃļéš, šö à ţýþö šüçĥ àš source: ƒöŕ source_language: đöéš ñöţ ļöàđ šîļéñţļý àš à ḿöñöļîñĝüàļ þŕöĵéçţ. requires: (à ḿàþ öƒ þļüĝîñ ñàḿé → šéḿṽéŕ çöñšţŕàîñţ) ĝàţéš ļöàđîñĝ: à ŕéçîþé đéçļàŕîñĝ requires: { myplugin: "^1.0" } ŕéƒüšéš ţö ļöàđ îñ à ƃîñàŕý ţĥàţ ĥàš ñöţ ŕéĝîšţéŕéđ ţĥé myplugin éẋţéñšîöñ.

Öñé öƒ ţĥöšé ķéýš ḿàý ƃé ţĥé þŕöĵéçţ'š çöñṽéŕĝéñçé ṽéñüé: à šéŕṽéŕ ţĥàţ ĥöļđš ţĥé çöñţéñţ ḿéḿöŕý, ŕüñš ţĥé ļööþ öñ öŕĝàñîšàţîöñ ķéýš, àñđ çàŕŕîéš à ŕéṽîéŵ ǫüéüé. Ţĥé ƒŕàḿéŵöŕķ'š îñţéŕéšţ îñ îţ îš éẋàçţļý ţŵö ƒîéļđš, url: àñđ converge:, šö îţ àšķš ţĥé ŕéĝîšţŕý ŵĥîçĥ éẋţéñšîöñ îš ţĥé ṽéñüé ŕàţĥéŕ ţĥàñ ļööķîñĝ ƒöŕ à ķéý ƃý ñàḿé: àñ Extension ŕéĝîšţéŕéđ ŵîţĥ Venue: true çļàîḿš ţĥàţ ŕöļé, àñđ à þļüĝîñ ḿàñîƒéšţ đéçļàŕéš îţ àš "venue": true öñ à šçĥéḿà éẋţéñšîöñ. KapiProject.Venue() ŕéţüŕñš ţĥé VenueBinding{Key, URL, Converge} öƒ ţĥé ƒîŕšţ ŕéĝîšţéŕéđ ṽéñüé éẋţéñšîöñ ţĥé ŕéçîþé çàŕŕîéš, VenueKey() ñàḿéš ţĥé ŕéĝîšţéŕéđ ṽéñüé ķéý ŵîţĥ ñö ŕéçîþé îñ ĥàñđ (šö à ḿéššàĝé àƃöüţ àñ àƃšéñţ ƃļöçķ çàñ šţîļļ ñàḿé ţĥé ƃļöçķ ţö àđđ), àñđ IsVenueKey(name) ţéšţš öñé ķéý. Àñ üñŕéĝîšţéŕéđ ķéý öƒ ţĥé šàḿé ñàḿé (à ŕéçîþé ļöàđéđ ƃý à ƃîñàŕý ŵîţĥöüţ ţĥé þļüĝîñ) ŕéþöŕţš ñö ṽéñüé àñđ ñö öþîñîöñ. Ţĥé ṽéñüé çļîéñţ (host/venue/schema) àļšö ŕéĝîšţéŕš preview àţ çöļļéçţîöñ šçöþé, đéçöđéđ öñļý ŵĥéñ ţĥé ṽéñüé ķéý îš þŕéšéñţ. Šéé Ç-01 ƒöŕ ţĥé ƒüļļ éẋţéñšîöñ ḿöđéļ.

Ṽàļîđàţîöñ Ŕüļéš

  • version îš ŕéǫüîŕéđ, ḿüšţ ƃé "v1". À ţöþ-ļéṽéļ ķéý ţĥé ŕéçîþé đöéš ñöţ ĥàṽé (content:, coordinates:) îš ŕéĵéçţéđ ƃý ñàḿé ŵîţĥ à ĥîñţ àţ ţĥé ķéý ţĥàţ çàŕŕîéš ţĥé îñţéñţ, ŕàţĥéŕ ţĥàñ çàþţüŕéđ àš àñ üñķñöŵñ éẋţéñšîöñ.
  • id îš öþţîöñàļ. Ŵĥéñ šéţ, ValidateID ŕéǫüîŕéš prj_ ƒöļļöŵéđ ƃý 20 ţö 64 çĥàŕàçţéŕš ƒŕöḿ a-z2-7, ŵĥîçĥ îš ŵĥàţ NewID ḿîñţš (128 ŕàñđöḿ ƃîţš àš ļöŵéŕçàšé ŔƑÇ 4648 ƃàšé32, 26 çĥàŕàçţéŕš).
  • Ƒöŕ éàçĥ collections[] éñţŕý:
    • Ƃàŕé éñţŕý: path îš ŕéǫüîŕéđ àñđ content ḿüšţ ƃé éḿþţý.
    • Ñàḿéđ çöļļéçţîöñ: path ḿüšţ ƃé éḿþţý (üšé content) àñđ content ḿüšţ ƃé ñöñ-éḿþţý; éàçĥ îţéḿ ŕéǫüîŕéš à ñöñ-éḿþţý path.
    • channel ŕéǫüîŕéš à name, àñđ ḿüšţ ŕéšöļṽé àĝàîñšţ ţĥé đéçļàŕéđ þŕöƒîļéš àš ǫüàļîƒîéđ profile/channel.
    • source_only: true îš ŕéĵéçţéđ ŵĥéñ ţĥé çöļļéçţîöñ öŕ àñý îţéḿ àļšö çàŕŕîéš à target öŕ target_languages.
    • coordinates ḿàý ñöţ ñàḿé product öŕ channel.
    • Àñ îţéḿ'š comments îš true, false, öŕ à ḿàþþîñĝ ŵĥöšé ķéýš àŕé directives, channel àñđ only. Éàçĥ đîŕéçţîṽé, ţĥéŕé àñđ üñđéŕ defaults.comments.directives, îš à ḿàŕķéŕ ţĥàţ îš ñöţ éḿþţý, šţàŕţš ŵîţĥ ñö ŵĥîţéšþàçé, ĥöļđš ñö ļîñé ƃŕéàķ àñđ îš đéçļàŕéđ öñçé; àñ îţéḿ'š ļîšţ ḿàý ñöţ ŕéþéàţ öñé ţĥé đéƒàüļţš đéçļàŕé. Ţĥé éŕŕöŕ ñàḿéš ţĥé ķéý, šüçĥ àš collections[0].content[1].comments.directives[0].
    • Àñ îţéḿ'š comments.channel àñđ defaults.comments.channel ŕéšöļṽé àţ ļöàđ àš à çöļļéçţîöñ'š channel đöéš, àñđ ţĥé éŕŕöŕ ñàḿéš ţĥé ķéý.
    • Àñ îţéḿ ţĥàţ šéţš comments.only ḿàý ñöţ àļšö çàŕŕý à target, target_languages, à redaction, öŕ à format.config öŕ format.preset (ContentItem.validateCommentsOnly). Ţĥé éŕŕöŕ ñàḿéš ţĥé îţéḿ, šüçĥ àš collections[1].content[0]: comments.only is set, so the item cannot have a target.
  • Éṽéŕý profiles: ķéý îš à šļüĝ, àñđ šö îš éṽéŕý çĥàññéļ îţ đéçļàŕéš; à çĥàññéļ îš đéçļàŕéđ àţ ḿöšţ öñçé þéŕ þŕöƒîļé. À þŕöƒîļé'š voice îš šĥàþé-çĥéçķéđ éẋàçţļý ļîķé defaults.voice (öñé öƒ profile_file, profile, pack, öŕ à ƃàŕé þàţĥ), à concept öñ ţĥé þŕöƒîļé öŕ öñ à çĥàññéļ ḿüšţ ƃé ŵĥîţéšþàçé-ƒŕéé, àñđ valid_from/valid_to ḿüšţ þàŕšé àš à đàţé öŕ àñ ŔƑÇ3339 îñšţàñţ.
  • defaults.merge.conflict_policy, defaults.memory.fuzzy_threshold (0..100), defaults.redaction.detectors, defaults.materialize àñđ defaults.voice àŕé éàçĥ šĥàþé-çĥéçķéđ. defaults.source_gate îš ŕéàđ ƃý ţĥé ŕüññéŕ, ŵĥîçĥ àþþļîéš checked ŵĥéñ îţ îš üñšéţ.
  • Éàçĥ ƒļöŵ ḿüšţ ĥàṽé àţ ļéàšţ öñé šţéþ
  • Éàçĥ šţéþ ḿüšţ ĥàṽé à ñöñ-éḿþţý tool ƒîéļđ (üñļéšš îţ üšéš parallel)
  • Šţéþš ŵîţĥ parallel çàñ öḿîţ tool (ţĥé þàŕàļļéļ ƃŕàñçĥéš þŕöṽîđé ţööļš)
  • Éàçĥ requires: éñţŕý ḿüšţ ĥàṽé à ñöñ-éḿþţý þļüĝîñ ñàḿé àñđ à ŵéļļ-ƒöŕḿéđ šéḿṽéŕ çöñšţŕàîñţ (^1.0, >=1.4.0, 1.4.0, ~1.4.2, öŕ *). Üñļéšš SkipRequiresCheck îš šéţ, éṽéŕý ñàḿéđ þļüĝîñ ḿüšţ ĥàṽé à ŕéĝîšţéŕéđ éẋţéñšîöñ ĝŕöüþ, éļšé ļöàđîñĝ ƒàîļš ŵîţĥ àñ îñšţàļļ ĥîñţ.
  • Éẋţŕàš àţ éàçĥ šçöþé àŕé ṽàļîđàţéđ àĝàîñšţ àñý ŕéĝîšţéŕéđ éẋţéñšîöñ šçĥéḿà.

Ñöţé: name îš öþţîöñàļ (yaml:"name,omitempty"); ţĥé ƒŕàḿéŵöŕķ đöéš ñöţ ŕéǫüîŕé îţ. KapiProject.Identity() ŕéàđš id ƒîŕšţ àñđ ƒàļļš ƃàçķ ţö name, àñđ ţĥàţ îš ţĥé ṽàļüé éṽéŕý þŕöĵéçţ-šçöþéđ ķéý îš đéŕîṽéđ ƒŕöḿ, šţàŕţîñĝ ŵîţĥ ţĥé çöñţéẋţ ĝŕàþĥ'š šçöþé (host.ProjectScope). project.SetField(proj, "id", …) îš ţĥé öñé ŵŕîţéŕ: îţ ŕéƒüšéš àñ éḿþţý ṽàļüé àñđ ŕéƒüšéš ţö ŕéþļàçé àñ îđ ţĥé ŕéçîþé àļŕéàđý çàŕŕîéš, šö àñ àþþļîéđ çĥàñĝé-šéţ çàññöţ ŕé-ķéý à þŕöĵéçţ.

Ƒîļé Þàţĥš

  • Çöñţéñţ þàţţéŕñš àŕé éẋþàñđéđ ṽîà core/project.ExpandGlob, ƃàçķéđ ƃý github.com/bmatcuk/doublestar/v4; ŕéçüŕšîṽé ** đîŕéçţöŕý ḿàţçĥîñĝ îš šüþþöŕţéđ (é.ĝ. src/**/*.json). ExpandGlob ƒîļţéŕš öüţ àñý ḿàţçĥ ţĥàţ ḿàţçĥéš öñé öƒ ţĥé defaults.exclude ĝļöƃ þàţţéŕñš (ḿàţçĥéđ ŵîţĥ doublestar.Match). À ļööķüþ ţĥàţ šţàŕţš ƒŕöḿ à þàţĥ àþþļîéš ţĥé šàḿé þàţţéŕñš: KapiProject.ItemForPath çļàîḿš ñö éẋçļüđéđ þàţĥ, šö à ñàḿéđ éẋçļüđéđ ƒîļé šîţš àţ ţĥé þŕöĵéçţ'š đéƒàüļţ þöîñţ
  • Çöñţéñţ ŕéšöļüţîöñ àļšö šķîþš à ƒîļé ţĥé þŕöĵéçţ'š îĝñöŕé ŕüļéš ḿàţçĥ: .kapiignore, KAPI_IGNORE àñđ ţĥé đéƒàüļţ ŕüļéš (core/ignore). À KapiProject ĥöļđš ñö þŕöĵéçţ đîŕéçţöŕý, šö ţĥé ĥöšţš àþþļý ţĥöšé ŕüļéš ŵĥéŕé ţĥéý ţüŕñ à ñàḿéđ þàţĥ îñţö à þöîñţ (host.ProjectIgnores): à ñàḿéđ îĝñöŕéđ ƒîļé šîţš àţ ţĥé þŕöĵéçţ'š đéƒàüļţ þöîñţ àñđ îñ ñö çöļļéçţîöñ, ƒöŕ kapi check, kapi voice guide, kapi context, à ŕüñ'š ƃîñđîñĝš àñđ ţĥé đéšķţöþ çöñţéẋţ þàñéš
  • Þàţţéŕñš àŕé ŕéšöļṽéđ ŕéļàţîṽé ţö ţĥé þŕöĵéçţ ŕööţ (ţĥé ŕéçîþé'š þàŕéñţ đîŕéçţöŕý)
  • target îš éẋþàñđéđ þéŕ šöüŕçé ƒîļé àñđ ţàŕĝéţ ļàñĝüàĝé ƃý core/project.ResolveTargetPath(itemPath, base, target, source, lang):
    • base ĥéŕé îš ţĥé îţéḿ'š öŵñ ƃàšé, ţĥé đîŕéçţöŕý ţĥé šöüŕçé þàţĥ îš ḿàđé ŕéļàţîṽé ţö, àƒţéŕ ţĥé çöļļéçţîöñ'š base ĥàš ƃééñ ƒöļđéđ îñ. Ŵĥéñ éḿþţý îţ đéƒàüļţš ţö GlobFixedPrefix(path), ţĥé ļîţéŕàļ þŕéƒîẋ öƒ ţĥé ĝļöƃ ƃéƒöŕé ţĥé ƒîŕšţ */?/[/{ (šö input/docs/*.md ḿîŕŕöŕš ĵüšţ ƒîļéñàḿéš ŵĥîļé input/**/*.md, öŕ àñ éẋþļîçîţ base, ḿîŕŕöŕš ţĥé šüƃţŕéé).
    • Ţöķéñš: {lang}, {relpath} (ŕéļ þàţĥ ŵîţĥ éẋţéñšîöñ), {path} (ŕéļ þàţĥ ŵîţĥöüţ éẋţéñšîöñ), {dir}, {filename}, {name} (àļîàš {basename}), {ext}; à ƃàŕé * îš šĥöŕţĥàñđ ƒöŕ {name}. {lang} îš ĥàñđļéđ ƃý ResolvePathPattern; ţĥé ŕéšţ ƃý ExpandTemplate.
    • Đîŕéçţöŕý-ḿîŕŕöŕ ƒöŕḿ: ŵĥéñ ţĥé ţàŕĝéţ (àƒţéŕ {lang} éẋþàñšîöñ) éñđš ŵîţĥ /, îš éḿþţý, öŕ îţš ƒîñàļ šéĝḿéñţ ĥàš ñö éẋţéñšîöñ àñđ ñö ŵîļđçàŕđ/ţöķéñ, îţ đéñöţéš à đîŕéçţöŕý: ţĥé šöüŕçé'š {relpath} (üñđéŕ base) îš àþþéñđéđ. Šö target: output/{lang} ḿîŕŕöŕš ţĥé šöüŕçé ţŕéé üñđéŕ éàçĥ þéŕ-ļàñĝüàĝé ŕööţ ŵîţĥ ñö ţöķéñ àñđ ñö đöüƃļéđ éẋţéñšîöñ. Šéé isDirectoryTarget îñ core/project/path.go.

Çŕéđéñţîàļ Ŕéšöļüţîöñ

Ţĥé kapi.yaml ŕéçîþé ŕéƒéŕéñçéš ÀÎ þŕöṽîđéŕš ƃý ţýþé (é.ĝ., provider: anthropic), ñöţ ƃý ķéý. ÀÞÎ ķéýš àŕé ŕéšöļṽéđ àţ ŕüñţîḿé:

  1. ÖŠ ķéýçĥàîñ ṽîà host/credentials.Store (ñöñ-šéçŕéţ çöñƒîĝ àţ providers.json üñđéŕ ţĥé üšéŕ çöñƒîĝ đîŕéçţöŕý: ~/.config/kapi öñ Ļîñüẋ, ~/Library/Application Support/kapi öñ ḿàçÖŠ; kapi config path þŕîñţš ţĥé ŕéšöļṽéđ ļöçàţîöñ. Ķéýš ļîṽé üñđéŕ ţĥé ķéýçĥàîñ šéŕṽîçé "kapi")
  2. Éñṽîŕöñḿéñţ ṽàŕîàƃļéš (ANTHROPIC_API_KEY, OPENAI_API_KEY) öŕ ţĥé --api-key ƒļàĝ
  3. Ţĥé --provider àñđ --model ÇĻÎ ƒļàĝš öṽéŕŕîđé þŕöĵéçţ đéƒàüļţš

ÇĻÎ Îñţéĝŕàţîöñ

# One-shot (no project)
kapi translate -i file.json --target-lang fr

# With project file: run a built-in flow with project defaults
kapi run translate-qa -p kapi.yaml --target-lang de

# Or run a flow defined in the recipe's flows: map (here named "translate")
kapi run translate -p kapi.yaml

Ƃüîļţ-îñ ƒļöŵš àŕé ŕéĝîšţéŕéđ îñ host/flowdef.BuiltInFlows; ţĥé kapi run ŕéƒéŕéñçé ļîšţš ţĥéḿ. À ŕéçîþé'š flows: ḿàþ àđđš ƒļöŵš, àñđ à ƃüîļţ-îñ ñàḿé ŵîñš öṽéŕ à ŕéçîþé ƒļöŵ öƒ ţĥé šàḿé ñàḿé îñ kapi run, šö à ŕéçîþé ƒļöŵ ţĥàţ šĥöüļđ ñöţ šĥàđöŵ à ƃüîļţ-îñ ţàķéš à ñàḿé öƒ îţš öŵñ.

Ŵîţĥ -p:

  • Ţĥé ƒļöŵ ñàḿé îš ḿàţçĥéđ àĝàîñšţ ţĥé ƃüîļţ-îñ ƒļöŵš ƒîŕšţ; îƒ îţ îš ñöţ öñé öƒ ţĥöšé, îţ îš ļööķéđ üþ îñ ţĥé þŕöĵéçţ'š flows ḿàþ (àñđ ƒîñàļļý ţĥé þļüĝîñ ƒàļļƃàçķ)
  • defaults.source_language àñđ defaults.target_languages[0] þŕöṽîđé đéƒàüļţš (ÇĻÎ ƒļàĝš öṽéŕŕîđé)
  • Ƒöŕ šîñĝļé-ƒîļé ƒļöŵš, --input šéļéçţš ţĥé ƒîļé. Ţĥé þŕöĵéçţ'š collections đéšçŕîƃé ŵĥîçĥ ƒîļéš kapi extract / kapi merge öþéŕàţé öñ àçŕöšš ţĥé þŕöĵéçţ

Đéšķţöþ Îñţéĝŕàţîöñ

Kapi Đéšķţöþ àţ apps/kapi-desktop/:

  • Öþéñš à þŕöĵéçţ ƃý îţš ƒöļđéŕ (ŵĥîçĥ çöñţàîñš kapi.yaml): Ƒîļé > Öþéñ, đŕàĝ-àñđ-đŕöþ
  • Éđîţš ƒļöŵš îñļîñé (šţéþš éđîţöŕ)
  • Ŕéšöļṽéš çöñţéñţ þàţţéŕñš àĝàîñšţ ţĥé ƒîļéšýšţéḿ ṽîà App.MatchContent(tabID), üšîñĝ ţĥé šàḿé core/project ĝļöƃ éẋþàñšîöñ ţĥé ÇĻÎ ŕéļîéš öñ ƒöŕ extract / merge; þàţţéŕñ ŕéšöļüţîöñ îš šĥàŕéđ ƒŕàḿéŵöŕķ çöđé, ñöţ à đéšķţöþ-öñļý ƒéàţüŕé
  • Šţöŕéš ŕéçéñţ ƒîļéš (recent.json) àñđ šéţţîñĝš (settings.json) îñ îţš öŵñ çöñƒîĝ ŕööţ (~/.config/kapi-desktop öñ Ļîñüẋ, ~/Library/Application Support/kapi-desktop öñ ḿàçÖŠ), öṽéŕŕîđàƃļé ŵîţĥ KAPI_DESKTOP_CONFIG_DIR (apps/kapi-desktop/backend/paths.go)

Éẋàḿþļé Ƒîļéš

Ḿîñîḿàļ

version: v1
name: Quick Translate

Ƒüļļ

version: v1
name: Acme App

defaults:
source_language: en
target_languages: [fr, de, ja]
concurrency: 4
parallel_blocks: 3
encoding: utf-8
exclude:
- "**/*.generated.json"
merge:
conflict_policy: translator-wins
memory:
fuzzy_threshold: 75
segmentation:
source: true
coordinates:
brand: acme
terms_source: .kapi/terms.json
memory_source: .kapi/memory/memory.json

profiles:
acme:
channels: [app, marketing]
voice: .kapi/voice.yaml

collections:
# Bare entry: single glob, languages inherited from defaults.
# Directory-mirror target: src/i18n/en/app.json → src/i18n/{lang}/app.json.
- path: "src/i18n/en/*.json"
target: "src/i18n/{lang}"

# Named collection: groups patterns, scopes languages, binds a channel, and
# names the directory it lives in. Paths and targets below are relative to it:
# marketing/en/docs/api.md → marketing/fr/docs/api.md.
- name: Marketing
channel: acme/marketing
coordinates:
mode: how-to
target_languages: [fr, de]
base: marketing
content:
- path: "en/docs/**/*.md"
target: "{lang}/docs"
- path: "en/site/**/*.html"
target: "{lang}/site"

# Source-only: read and checked, never translated.
- name: packaging
channel: acme/app
source_only: true
content:
- path: "packaging/nfpm.yaml"

preset: nextjs
requires:
okapi-bridge: ">=1.47.0"

flows:
translate:
steps:
- tool: translate
config:
provider: anthropic

full-pipeline:
steps:
- tool: recycle
config:
fuzzyThreshold: 75
- tool: translate
config:
provider: anthropic
- tool: qa

pseudo:
steps:
- tool: pseudo-translate
config:
expansionPercent: 30