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:

StepFunctionTemplateOutput
ModelgenerateModelFileModelTemplatemodel.go โ€” the Go struct
InterfacegenerateInterfaceFileInterfaceTemplateinterface.go โ€” the service contract
ServicegenerateServiceFileServiceTemplateservice.go โ€” *entityService stub implementing the interface, with any --description text turned into a TODO: comment
gRPC/ConnectRPC servicegenerateGRPCServiceFileGRPCServiceTemplategrpc/service.go โ€” the RPC handler wrapping the service
ConsumergenerateConsumerFileConsumerTemplateconsumer.go โ€” Kafka consumer stub
MigrationgenerateMigrationFileMigrationTemplatelib/repo/migrations/<NNNNNNNN>_<entity>.up.sql, sequentially numbered by scanning the existing migrations directory
RepositorygenerateRepoFileRepositoryTemplatelib/repo/<entity>.go โ€” squirrel-based Postgres repo
CLI commandgenerateCLICommandFileCLICommandTemplatecmd/<entity>_cmd.go โ€” a Cobra subcommand wired to the entity
TestsGenerateUnitServiceTestFile / 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/worker in a generated project)
  • GenerateDockerFiles โ€” Dockerfile, Dockerfile.worker, docker-compose.yml
  • GenerateIntegrationTestMainFile โ€” shared TestMain for every entity’s integration test
  • GenerateStaticFiles โ€” files that don’t vary by entity: cmd/app.go (the generated gRPC client the CLI dials into, mirroring Beaver’s own cmd/app.go), cmd/form/form.go, cmd/{create,get,list,update,delete}.go command scaffolding, lib/repo/conn.go, lib/repo/helpers.go
  • generateCLIRootFile / generateProjectMainFile โ€” cmd/root.go and the project’s root main.go
  • GenerateGitignore, GenerateMakefile, GenerateGolangCI, GenerateCLAUDEMD โ€” the rest of the skeleton: .gitignore, Makefile, .golangci.yml, and a generated CLAUDE.md so 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-context prints a complete, structured reference of the command tree, flags, and workflows โ€” written to be pasted straight into an LLM context window.
  • beaver skill prints the step-by-step agent playbook, including the manual fixes that are always required after generate all runs.

Known gaps

  • cmd/generate_init.go’s --registry flag and the pbuf-registry service in docker-compose.yml point at an optional local protobuf registry (see feat/registry-integration); the default path still targets buf.build.
  • generate ui is referenced in cmd/root.go’s help text and beaver llm-context, but there is currently no cmd/generate_ui.go implementing 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.