Skip to content

.corpus.yaml

.corpus.yaml sits at the root of a corpus (one repository of knowledge records kept in git). It states what the corpus is, which types it adopted, where it is published, and where it takes the shared framework from.

new writes the file when the corpus is created, commented key by key. You edit it by hand after that. update rewrites four of the keys below and never replaces the file. A worked copy is examples/library/.corpus.yaml.

The file says which types the corpus adopted, and .schema/ says what a record of each type must have. --from, --ref, --path and --policy each replace a value here for one run only.

Every key

Key What it states Who writes it
descriptor-version the format of this file new, then update
corpus the name this corpus publishes under new
shortcode the word another corpus cites this one by you
content-version the version of what this corpus knows new, then you
display-name what a person calls this corpus you
description what this corpus is, in a sentence you
license the licence it publishes under you
author who publishes it, as a name and a URL you
publishing-target how the corpus is published new
publishing.base the URL a person opens to browse it new, then you
publishing.path-prefix where the corpus sits inside its repository you
repos-base where this estate's code repositories live you
tracker where work about this corpus's records is filed you
framework where to report a problem with the framework new
upstream.url, .path, .ref where the framework is taken from new, then you
upstream.commit, .template-version, .taken-on what the last take resolved to update
update-policy how far an update goes new
types the types this corpus adopted new, then update --add-type
enums the ranges the schema leaves to the corpus you
consumes the corpora this one reads you, and restore resolves
export.exclude what the export leaves behind new
plugin.from where the plugin tree is read from you
skip the files this corpus owns you

Identity

Three keys state a version: descriptor-version for the format of this file, content-version for what the corpus knows, and upstream.template-version for the template shape it last took.

corpus

The name this corpus publishes under.

corpus: knowledge-as-code

An export states it, so a consumer with several exports can tell whose vocabulary it is reading.

shortcode

The shorthand another corpus cites this one by, as the eng in eng:pol-VURM.TIMEBOX.

shortcode: kac

validate checks the spelling, and the shortcode says what a legal one looks like. Leave it out until something cites this corpus. new writes it bare, and an export from a corpus with none states null.

content-version

The version of what this corpus knows.

content-version: "0.1.0"

It is semantically versioned. Move the major where a meaning changed or a published URL broke. Move the minor for additions, and the patch for wording. Nothing refuses to load because the number moved.

descriptor-version

The format of this file.

descriptor-version: 5

update stamps it with the format the tool writes, so a corpus records which shape it last took.

What a reader sees outside the repository

These four keys are for a reader who meets this corpus as a package or an installed plugin.

display-name: Knowledge as Code
description: >
  The engineering knowledge of one organisation, held as records a person and an agent read the same way.
license: MIT
author:
  name: A Person
  url: https://github.com/a-person

export writes all four into the export, pack writes them into the package a registry lists, and bundle writes them into the plugin manifest somebody installs.

Leave a key bare and all three omit it. new writes all four bare.

Where the corpus is published

publishing-target

How the corpus is published, which decides whether export builds a link to each record and in which form.

publishing-target: github

The export format says how a record's path and a part's anchor join the base.

github means the repository is itself the published form, and a record is read rendered on github.com. Write the repository's own URL, with no /blob on the end. export adds that segment along with the commit.

azure-devops means the corpus lives in Azure Repos and no wiki publishes it. Write the repository's _git URL, as https://dev.azure.com/{org}/{project}/_git/{repo}.

azure-devops-wiki means an Azure DevOps wiki publishes it. Write the wiki's own URL, as https://dev.azure.com/{org}/{project}/_wiki/wikis/{wiki}, and nothing after it. Once you open a page, the address bar shows a numeric page id. export refuses a base with one in it and builds no links. A link to a wiki page is not pinned to a commit, so a person following it reads whatever the wiki has now. The commit travels in the manifest.

none means the corpus is not published. Its export has no links and says so in its manifest. It is the one value that needs no publishing: block.

validate refuses any other value, and its message lists the values it takes. It refuses mkdocs, because export cannot build a link to a page on an MkDocs site.

publishing

Where the published corpus is served from, and nothing else.

publishing:
  base: https://github.com/paul80nd/knowledge-as-code
  path-prefix: examples/library
Key What it states Who writes it
base the URL a person opens to browse the corpus new, then you
path-prefix where the corpus root sits inside that repository you

Set path-prefix where the repository contains more than the corpus. Do not append that folder to base: export inserts the commit between base and the record's path. Leave the key out where the corpus is the repository.

Where the code is

repos-base

Where this estate's code repositories are addressed from. A services record names each repository under repos as <repo> or <group>/<repo>, spelt as the host spells it, and kac resolves the entry against this base.

repos-base: https://git.example.com/example-libraries

On most hosts an entry resolves to <repos-base>/<entry> as written, so paul80nd/knowledge-as-code against https://github.com resolves to that repository. On Azure DevOps, the path of the base decides the form of an entry:

repos-base Entry Resolves to
https://dev.azure.com/acme or https://acme.visualstudio.com <project>/<repo> <base>/<project>/_git/<repo>
https://dev.azure.com/acme/platform or https://acme.visualstudio.com/platform <repo> <base>/_git/<repo>
https://dev.azure.com/acme/platform/_git <repo> <base>/<repo>

Set the base to the organisation where the estate's repositories sit in several projects:

repos-base: https://dev.azure.com/acme

mirrors-repo-links compares the resolved set against the links a record writes under Where it lives, in both directions, and warns where the two disagree. It percent-decodes each link and ignores case, so Proj%20X/_git/My%20Repo matches the entry Proj X/My Repo. On Azure DevOps, it also warns on an entry in the wrong form and names the form the base needs. It reports nothing in a corpus that states no base, and nothing in a corpus that has not adopted services.

One base covers one host. On Azure DevOps it covers one organisation, and a link on dev.azure.com/acme matches one on acme.visualstudio.com.

Where work is filed

tracker

Where work about this corpus's records is filed.

tracker:
  target: azure-devops
  base: https://dev.azure.com/acme/Standards
  area: kac-it-swdev
Key What it states Who writes it
target the client that opens a ticket on the tracker you
base the repository or project the backlog belongs to you
area the area path inside an Azure DevOps project to file in you

Leave the block out where the backlog belongs to what publishing: already names. export then derives the address by the table below. A corpus published on GitHub states nothing.

State the block where the backlog and the published form are two places, as an Azure DevOps project is.

target is one of github, azure-devops or none. Write none where the corpus has no tracker at all. validate refuses any other value.

Write the project URL for azure-devops, as https://dev.azure.com/{org}/{project}. A URL naming a repository or a wiki inside the project is cut back to the project.

area

area is the area path below that project a new work item is filed under. Write it separated by /, the way base is already written, as kac-it-swdev or kac-it-swdev/subteam. The client that files converts it to the backslashes Azure Boards uses.

State an area in every corpus that shares its Azure DevOps project with another corpus. The harvest-findings skill triages only the findings in a corpus's own area, and in a corpus without one it reads the whole project. A corpus alone in its project needs none. Leave it out and an item lands in the project's default area.

area applies to azure-devops alone, and validate refuses it beside github or none. A block stating no target is judged by the target publishing-target implies. Nothing derives area, and new and update never write it.

validate does not check the path against Azure DevOps. An area that does not exist fails when the item is created.

What export derives

publishing-target The tracker derived from base
github the repository, which is its own issue list
azure-devops the project holding the repository
azure-devops-wiki the project holding the wiki
none none

Write base alone where your backlog moved inside the platform you publish on, and the client follows from publishing-target. Write target alone where the corpus has no tracker, and nothing states a base.

State both keys where you state a target publishing-target does not imply. A base is taken from the publishing block only where the two targets agree, and otherwise export writes no base.

framework

Where to report a problem with the framework itself: kac, the schema, the template or a skill.

framework:
  target: github
  base: https://github.com/paul80nd/knowledge-as-code
Key What it states Who writes it
target the client that opens an issue on the tracker new
base the repository or project the tracker belongs to new
area the area path inside an Azure DevOps project to file in you

export writes the block into the manifest, so an agent that meets your corpus as an installed plugin has the address.

target takes the three values tracker takes, and area follows the tracker rule. Write none, or leave the block out, where you know of no tracker.

new writes target and base when the corpus is created. It takes base from --from where that named a repository, and from the framework's own repository where it named a folder. It reads target from the host, and writes none for a host that neither gh nor az serves. Nothing derives this block.

Three addresses, and telling them apart

tracker.base is where a problem with one of your records goes. framework.base is where a problem with kac, the schema, the template or a skill goes. Each corpus in consumes: brings a third, which is its producer's. The export format says how a reader tells them apart when one team writes the same address twice.

Where the framework comes from

upstream

Where the corpus takes the framework from, and what the last take resolved to.

upstream:
  url: https://github.com/paul80nd/knowledge-as-code
  path: template
  ref: main
  commit: 1d5b531d5bae80dc9805dc501ccc6fe6aa2b4141
  template-version: 11
  taken-on: "2026-08-24"
Key What it states Who writes it
url the repository or folder serving the template you
path the folder inside it holding manifest.yaml you
ref the branch or tag to take from, followed on every update you
commit what the last take resolved to, as a full-length sha update
template-version the template shape that take was on update
taken-on the day the framework last came down update

update reads url when no --from is passed. Leave path out where the manifest sits at the repository root.

update follows ref. It writes commit for your information and never reads it. A template read from a folder resolves no commit, so commit is left as it stands, and new leaves ref beside it bare.

A corpus has one upstream, which it receives as files and keeps. It may have any number of consumes entries, whose records it reads and never commits. upstream.url says where the template was copied from, and framework.base says where to report a problem with it.

update-policy

How far an update goes. One of cautious or full, and cautious is the default.

update-policy: cautious
Value Seed files (a type's root page, its _template.md, the agent guidance)
cautious written only where the corpus has none
full refreshed as well, and you reconcile the result from the diff

update writes overlay files under either value. Layers says which files are which.

types

The types this corpus adopted.

types:
  - adrs
  - policies

Validation, index generation and what an update writes all cover the types listed here and no others.

new writes the list. Omit the key and kac reads adoption off the folders it finds, counting a type where both its page and its folder are there.

Once the key is declared, validate reports a type in the list that has no page or folder, and a type with both that is not in the list.

Use update --add-type and update --drop-type to change the list. Each moves the name and the type's files together.

enums

The ranges the schema leaves to the corpus.

enums:
  component-type: [asset, cli, website]
  platform: [dotnet, static]

A field declaring values: $corpus.<name> in .schema/ takes its range from here. .schema/services.yaml declares platform and component-type that way. Write each range here, and validate checks every record against it. The services type page says how to derive the two lists from your own deployables.

Write each value in lower case. enum-lowercase refuses a value such as Dotnet-Web in a record, so no record can satisfy a range containing one.

new writes the key with nothing under it. Write a record with such a field before you write the range, and validate reports corpus-enum-undeclared once, against .corpus.yaml. A required field is asked from the first record of its type, and an optional one from the first record that states it.

A value the range does not admit is an ordinary enum failure, reported against the record, and the message quotes the range.

consumes

The corpora this one reads.

consumes:
  - corpus: example-engineering
    shortcode: eng
    version: ^0.1.0
    source: https://nuget.pkg.github.com/OWNER/index.json
    resolved: "0.1.0"
Key What it states Who writes it
corpus the name the producer publishes the package under you
shortcode the word you cite it by, as the eng in eng:pol-VURM you
version the range you mean, as 0.1.0 or ^0.1.0 you
source the registry or folder the package is fetched from you
resolved the version the last restore took restore

restore fetches each entry and unpacks it under .imports/<shortcode>/, which is not committed. resolved is the lock, so there is no lock file. A source is a registry's service index, or a path to a folder holding what a producer packed. restore says how a range resolves and what a run refuses.

Leave the key out where this corpus stands on its own.

export

What the export leaves behind.

export:
  exclude: [ ]

It is empty by default, so a record in draft travels, and so does one whose review date has passed. Each states its own status. A consumer sees no sign of a record the list withheld.

Value Drops a record
draft whose status is draft
overdue whose review-by is in the past

Set it where a draft would be read as an answer, as a fix in draft would be by fix-lookup.

plugin

Where bundle reads the plugin tree from, meaning the skills and hooks an agent installs.

plugin:
  from: ../../shared/.plugin

Omit the key and bundle reads .plugin/ at the corpus root, which is what new creates. Set it and bundle reads one tree elsewhere, resolved against the corpus root, so several corpora in a repository share one copy. A shared plugin tree says what update sends a corpus that sets it.

The manifest, .plugin/.claude-plugin/plugin.json, stays in each corpus and is never read from the shared tree. A file a corpus writes under its own .plugin/ wins over the shared tree's copy of the same path.

skip

The files this corpus owns.

skip:
  - path: .plugin/hooks/breadcrumb
    reason: Patched for our proxy.

update neither reads nor writes a path listed here, and reports each file it stepped over. Each entry gives a path and a reason, for whoever opens the file next. Delete an entry once the file matches the framework again. Layers says which files update would otherwise write.

The export format is the page for what a consumer reads out of a corpus.