CI / test (push) Successful in 1m41s
There was no reference. Four narrative documents explain how to install and operate llmgw, and the only way to learn that `lease_ttl` exists was to read the one that happened to mention it — or the struct. Written by hand it would have been wrong within a fortnight. Six settings arrived in the last two weeks alone: store, servers, lease_ttl, the two retention windows, min_disk_gb. A reference nobody maintains is worse than none, because it is the same list with the setting you came to look up quietly missing from it. So it is derived from the structs, with the field comments as the descriptions — which means the only way for it to be wrong is for the code to be wrong, and it makes the comments worth writing twice over. 1163 lines, 24 blocks, 87 keys. Two tests keep that true after everyone has forgotten this exists. One regenerates and compares, so adding a setting without regenerating is a failure rather than an omission — checked by adding one and watching it fail. The other walks the tree for koanf tags and fails on a configuration file that is in neither the source list nor the short list of structs an operator never writes, because a list is a thing to forget. The generator finds the repository from its own path rather than the working directory, so `go generate ./...` produces the same document from anywhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
100 lines
3.1 KiB
Go
100 lines
3.1 KiB
Go
package docgen
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Sources are the files configuration is declared in.
|
|
//
|
|
// Listed rather than discovered, because "every file with a koanf tag" would
|
|
// sweep in structs that are configuration in the Go sense and not in the
|
|
// operator's — a scoring weight built at wiring time is not something anyone
|
|
// writes in a file. A test checks the list against what is actually tagged, so
|
|
// a new configuration file is a failure here rather than a silent omission.
|
|
var Sources = []string{
|
|
"internal/config/config.go",
|
|
"internal/agent/agent.go",
|
|
"internal/agent/discovery/discover.go",
|
|
"internal/agent/engines/engine.go",
|
|
"internal/agent/engines/llamacpp.go",
|
|
"internal/agent/engines/vllm.go",
|
|
"internal/agent/engines/sglang.go",
|
|
"internal/agent/engines/sim.go",
|
|
"internal/agent/supervisor/supervisor.go",
|
|
"internal/tlsconf/tlsconf.go",
|
|
"internal/control/catalog/catalog.go",
|
|
"internal/router/admission/controller.go",
|
|
"internal/accounting/cost.go",
|
|
}
|
|
|
|
// notReference are files with koanf tags that an operator never writes.
|
|
//
|
|
// Inventory and catalog entries carry them so the same structs can be decoded
|
|
// from an API payload or a model file; putting them in a configuration
|
|
// reference would invite somebody to write them into server.yaml.
|
|
var notReference = []string{
|
|
"internal/control/inventory/inventory.go",
|
|
}
|
|
|
|
// Render turns collected blocks into the reference document.
|
|
func Render(blocks []Block) string {
|
|
var b strings.Builder
|
|
b.WriteString(`# Configuration reference
|
|
|
|
Every setting llmgw reads from a file, with the type it takes and what it is
|
|
for.
|
|
|
|
**Generated from the source.** The descriptions are the comments on the fields
|
|
themselves, so this cannot describe a setting that does not exist or miss one
|
|
that does — and a test fails when it has drifted. Do not edit it; edit the
|
|
comment and run ` + "`go generate ./...`" + `.
|
|
|
|
Where a setting takes another block, the block is listed under its own heading
|
|
rather than repeated.
|
|
|
|
`)
|
|
|
|
// An index first: the reason to open a reference is usually to find one
|
|
// key, and scanning thirteen headings for it is the thing this replaces.
|
|
b.WriteString("## Every key\n\n| Key | Type | In |\n|---|---|---|\n")
|
|
for _, bl := range blocks {
|
|
for _, f := range bl.Fields {
|
|
b.WriteString(fmt.Sprintf("| `%s` | %s | [%s](#%s) |\n",
|
|
f.Key, code(f.Type), bl.Name, anchor(bl.Name)))
|
|
}
|
|
}
|
|
b.WriteString("\n")
|
|
|
|
for _, bl := range blocks {
|
|
fmt.Fprintf(&b, "## %s\n\n", bl.Name)
|
|
if bl.Doc != "" {
|
|
b.WriteString(bl.Doc + "\n\n")
|
|
}
|
|
for _, f := range bl.Fields {
|
|
fmt.Fprintf(&b, "### `%s`\n\n%s\n\n", f.Key, code(f.Type))
|
|
if f.Doc != "" {
|
|
b.WriteString(f.Doc + "\n\n")
|
|
}
|
|
if f.Nested != "" && hasBlock(blocks, f.Nested) {
|
|
fmt.Fprintf(&b, "Takes a [%s](#%s) block.\n\n", f.Nested, anchor(f.Nested))
|
|
}
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func hasBlock(blocks []Block, name string) bool {
|
|
for _, b := range blocks {
|
|
if b.Name == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func code(s string) string { return "`" + s + "`" }
|
|
|
|
// anchor is how GitHub and Gitea slug a heading.
|
|
func anchor(s string) string { return strings.ToLower(s) }
|