How kac is put together¶
kac is a .NET 10 command-line tool. A thin entrypoint parses the command line and hands each verb to kac.core, a
library that does the work. Every rule about a record comes from YAML files under .schema/, which kac reads at run
time. A corpus is one repository of knowledge records kept in git, and a record is one Markdown document in it. An
export is the set of files a corpus publishes for another corpus to consume.
dotnet run --project tooling/kac -- --help # build the entrypoint from source and list its verbs
The parts¶
| Part | What it contains |
|---|---|
tooling/kac/ |
Program.cs, which declares each verb, parses its options and reads the clock, the environment, git and the network |
tooling/kac.core/ |
Loading a corpus, every check, generation, export, bundling, packing, restore, update and reports |
.schema/ |
One YAML file per type (one kind of record), plus the shared fields, tiers, enums and checks |
manifest.yaml and template/ |
The files kac new sends a new corpus and kac update sends an existing one |
tooling/kac.tests/ and tooling/kac.features/ |
The unit tests and the Reqnroll behaviour specs |
tooling/kac.goldens/ and tooling/tests/ |
The golden suite, its fixtures, and the two round-trip scripts |
.schema/README.md lists every schema
file and what it declares.
kac.core depends on YamlDotNet and Markdig. The entrypoint adds Spectre.Console.Cli, which generates --help
for every verb and option.
How kac finds a corpus and its schema¶
kac makes two searches, both upward from the working directory:
- The nearest folder holding a
.corpus.yamlis the corpus. Where none exists,kacexits with code 2. - The nearest folder at or above the corpus holding a
.schema/folder gives the schema. Where none exists,kacexits with code 1.
In this repository, each corpus under examples/ and template/ finds the one .schema/ at the root.
--version and --help need neither search, so both answer from any folder.
The order a command runs in¶
kac validate shows the order every verb follows:
Spectre.Console.Cliparses the arguments. An option the verb does not declare stops the run with exit code 1.Cli.InCorpusfinds the corpus and the schema.Program.csreads today's date, builds the HTTP client and reads which CI platform is running it. It then callsCommands.Validate.Corpus.Loadreads.corpus.yaml, asks git for the corpus's files, loads the schema and reads.imports/. Discovery then selects the records from that listing and parses each one.Validator.CheckAllchecks the schema first, then each record, then the corpus as a whole. It returns a list of findings and prints nothing.Commandsprints the findings and returns the exit code.
Where the console is¶
kac.core returns values, and only Commands writes to the console. Validator.CheckAll returns findings,
New.Plan and Update.Plan each return a plan, and GeneratedFiles.Plan returns the files generate would write.
Commands derives the exit code from the value it was handed.
A plan is decided before anything is done. New.Apply, Update.Apply and GeneratedFiles.Write act on exactly the
files their plan reports, so nothing is decided twice.
Testing without a disk¶
Tree stands between every check and the disk. It is a listing of paths, a function that reads one, and a function
that says whether a path is on the disk. A test builds a Tree from strings, so it can validate a corpus that was never
written to disk. A check asks the listing whether a file is present, and calls Tree.OnDisk only where Tree says to.
The rest of kac.core follows the same pattern:
New.PlanandUpdate.Plantake file listings, a manifest and a function saying whether two copies of a path match. A new case in either is a unit test, not a fixture corpus.Corpus.LoadandSchema.Loadeach take either values or a path. The path overload is the only place a path becomes a corpus or a schema.
Where a check lives¶
Most checks are data. A rule in .schema/<type>.yaml with an expr: needs no C#, and kac picks it up at load.
A check that needs C# is a class under tooling/kac.core/Rules/, or a core check under tooling/kac.core/Checks/.
.schema/_checks.yaml declares every check id, whichever kind reports it.
Checks in C# says when a rule needs a class and how to add one.
Where a reverse edge is published¶
Where a reverse edge is read decides which surface publishes it. A record states the edges from it to other records, and a reverse edge lists the records that point at it. A surface is the file or command output where a reader finds that list. A plugin is a corpus packaged for Claude Code.
| Where it is read | Surface | How it is published |
|---|---|---|
| Within one corpus | A reciprocal: field |
The schema edge declares it. The author writes both ends, and validate checks they agree. |
| By an agent with the plugin installed | A view | bundle renders it from the export. For example, the review view lists the controls for each rule. |
| Across corpora | A report | kac report reads .imports/. For example, coverage lists what discharges each policy clause. |
The export adds no file or field for a reverse edge.
Where to go next¶
Build, test and release builds kac from a clone and runs every test layer.