Architecture
Beaver is a Cobra CLI (main.go โ cmd/) wrapped around a code-generation library (lib/). Nothing it generates is runtime-linked to Beaver itself โ Beaver reads .proto files, renders Go text/template templates against the parsed schema, and writes the result to disk. This document walks the pipeline end-to-end, in the order a new service is actually built.
1. Schema-first input
Everything starts with a protobuf schema under schemas/<service>/v<N>/:
schemas/<service>/v1/
model.proto # message (entity) definitions โ the source of truth
service.proto # optional hand-written RPC service definition
services/ # generated service-level protos (beaver generate init)
beaver generate init <service> bootstraps this by parsing model.proto (ParseProtoFile with SkipService: true), calling GenerateServiceProtoFile to emit a service-level proto stub under services/, and writing a buf.gen.yaml/buf.yaml pair via GenerateBufFile (cmd/generate_init.go, lib/generator.go).
beaver build protobuf <service> (aliases pb, proto, grpc) then shells out to protoc with protoc-gen-go/protoc-gen-go-grpc to compile the .proto files into Go bindings (cmd/buildProtobuf.go). Proto files inside a services/ directory are skipped by the entity-generation commands โ they’re already-compiled RPC contracts, not entities.
2. Parsing: proto โ TemplateData
ParseProtoFile (lib/base.go) uses github.com/yoheimuta/go-protoparser/v4 to walk a .proto file’s AST and produce a TemplateData struct: the Go package name, entity name, every top-level message (MessageData, with fields and their inferred Go types), enums, services, and imports. The identifier field on every message is expected to be named uuid (not id) โ this convention is baked into the generated repository and gRPC layers. ParseMultipleProtoFiles/BuildProjectTemplateData fold several parsed files into one ProjectTemplateData for the templates that need to know about every entity at once (main.go, worker.go, the CLI root, Docker files).
3. Per-entity generation (generateEntityFiles / GenerateAllFiles, lib/generator.go)
For each parsed message, Beaver renders a fixed set of templates (lib/templates.go) into a fixed layout, all under lib/<package>/<entity>/ unless noted:
| Step | Function | Template | Output |
|---|---|---|---|
| Model | generateModelFile | ModelTemplate | model.go โ the Go struct |
| Interface | generateInterfaceFile | InterfaceTemplate | interface.go โ the service contract |
| Service | generateServiceFile | ServiceTemplate | service.go โ *entityService stub implementing the interface, with any --description text turned into a TODO: comment |
| gRPC/ConnectRPC service | generateGRPCServiceFile | GRPCServiceTemplate | grpc/service.go โ the RPC handler wrapping the service |
| Consumer | generateConsumerFile | ConsumerTemplate | consumer.go โ Kafka consumer stub |
| Migration | generateMigrationFile | MigrationTemplate | lib/repo/migrations/<NNNNNNNN>_<entity>.up.sql, sequentially numbered by scanning the existing migrations directory |
| Repository | generateRepoFile | RepositoryTemplate | lib/repo/<entity>.go โ squirrel-based Postgres repo |
| CLI command | generateCLICommandFile | CLICommandTemplate | cmd/<entity>_cmd.go โ a Cobra subcommand wired to the entity |
| Tests | GenerateUnitServiceTestFile / GenerateIntegrationRepoTestFile | โ | unit test (needs mockery mocks, so only run from generate all) and a //go:build integration repo test |
Every write goes through processTemplate โ writeFileWithMode, which respects one of three write modes: WriteModeSkip (default โ never clobber hand-written business logic), WriteModeOverwrite (used for files that are always machine-derived, like the model struct or migrations), or force/backup, controlled by the -f/--force and -b/--backup flags shared across generate subcommands.
generate all runs every step above for every proto file; the narrower subcommands (generate entity, generate interface, generate service, generate consumer) run a subset for iterative regeneration after a schema change. beaver add entity does the same as generate all for a single new proto file, but also re-registers the entity in the manifest so project-wide (tail) files stay in sync.
4. Project-wide tail files (generateProjectTailFiles, lib/generator.go)
Once every entity’s files exist, GenerateProjectFromProtoFiles builds a ProjectTemplateData covering all entities and generates the files that need the full picture:
GenerateAPIFile/GenerateWorkerFileโmain.go-equivalents for the API and async worker binaries (cmd/api,cmd/workerin a generated project)GenerateDockerFilesโDockerfile,Dockerfile.worker,docker-compose.ymlGenerateIntegrationTestMainFileโ sharedTestMainfor every entity’s integration testGenerateStaticFilesโ files that don’t vary by entity:cmd/app.go(the generated gRPC client the CLI dials into, mirroring Beaver’s owncmd/app.go),cmd/form/form.go,cmd/{create,get,list,update,delete}.gocommand scaffolding,lib/repo/conn.go,lib/repo/helpers.gogenerateCLIRootFile/generateProjectMainFileโcmd/root.goand the project’s rootmain.goGenerateGitignore,GenerateMakefile,GenerateGolangCI,GenerateCLAUDEMDโ the rest of the skeleton:.gitignore,Makefile,.golangci.yml, and a generatedCLAUDE.mdso an AI assistant working in the new repo picks up its conventions immediately
This is also where the “app skeleton” comes together in practice: go.mod (GenerateGoModFile, WriteModeSkip so an existing module is never rewritten) and the rest of the scaffold are generated as a side effect of running generate all / add entity, not as a separate up-front step.
5. The manifest
After generate all (and add entity), Beaver parses each proto file again and calls UpdateManifestFromTemplateData to record the entity, its fields, and its generated file paths into .beaver.yml at the project root, alongside service name/description and created/updated timestamps. beaver manifest show/init read and write this file; it’s intended as a foundation for future cross-service discovery, and generated services aren’t expected to hand-edit it.
Reference commands
beaver llm-contextprints a complete, structured reference of the command tree, flags, and workflows โ written to be pasted straight into an LLM context window.beaver skillprints the step-by-step agent playbook, including the manual fixes that are always required aftergenerate allruns.
Known gaps
cmd/generate_init.go’s--registryflag and thepbuf-registryservice indocker-compose.ymlpoint at an optional local protobuf registry (seefeat/registry-integration); the default path still targetsbuf.build.generate uiis referenced incmd/root.go’s help text andbeaver llm-context, but there is currently nocmd/generate_ui.goimplementing it โ UI scaffolding isn’t wired up yet.beaver create(cmd/create.go) is a registered root command with no subcommands yet; it’s reserved for future project-level scaffolding.