.claude/skills/do-proto/SKILL.md
Define proto files for a domain
npx skillsauth add viqueen/claude-go-playground .claude/skills/do-protoInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Add the API contract for a domain. No Go business logic, no SQL — just the proto definition. This PR is auditable as: "Is the API contract right?"
All file paths are relative to the chosen project: connect-rpc-backend/ or grpc-backend/.
The user will specify which project. All make commands must be run from the project root.
The user will specify:
content, workspace, user)protos/<domain>/v1/Split into three files per domain:
protos/<domain>/v1/<domain>_model.proto — Resource + Enumssyntax = "proto3";
package <domain>.v1;
import "buf/validate/validate.proto";
import "google/protobuf/timestamp.proto";
option go_package = "<domain>/v1;<domain>v1";
enum <Resource>Status {
<RESOURCE>_STATUS_UNSPECIFIED = 0;
<RESOURCE>_STATUS_DRAFT = 1;
<RESOURCE>_STATUS_PUBLISHED = 2;
<RESOURCE>_STATUS_ARCHIVED = 3;
}
message <Resource> {
string id = 1;
string title = 2 [(buf.validate.field).string = {min_len: 1, max_len: 255}];
string body = 3 [(buf.validate.field).string.min_len = 1];
<Resource>Status status = 4 [(buf.validate.field).enum = {defined_only: true}];
repeated string tags = 5;
google.protobuf.Timestamp created_at = 6;
google.protobuf.Timestamp updated_at = 7;
}
Conventions:
_UNSPECIFIED = 0google.protobuf.Timestamp for time fields (not strings)go_package uses the alias format: <domain>/v1;<domain>v1buf/validate annotations — these are enforced when the model is embedded in Update requests with FieldMaskspace.v1.SpaceRef) in the model, not plain string IDsprotos/<domain>/v1/<domain>_refs.proto — Typed ID Referencessyntax = "proto3";
package <domain>.v1;
import "buf/validate/validate.proto";
option go_package = "<domain>/v1;<domain>v1";
message <Resource>Ref {
string id = 1 [(buf.validate.field).string.uuid = true];
}
Conventions:
string id fields directlyprotos/<domain>/v1/<domain>_service.proto — Service + Request/Responsesyntax = "proto3";
package <domain>.v1;
import "buf/validate/validate.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/field_mask.proto";
import "<domain>/v1/<domain>_model.proto";
option go_package = "<domain>/v1;<domain>v1";
service <Resource>Service {
rpc Create<Resource>(Create<Resource>Request) returns (Create<Resource>Response);
rpc Get<Resource>(Get<Resource>Request) returns (Get<Resource>Response);
rpc List<Resource>(List<Resource>Request) returns (List<Resource>Response);
rpc Update<Resource>(Update<Resource>Request) returns (Update<Resource>Response);
rpc Delete<Resource>(Delete<Resource>Request) returns (google.protobuf.Empty);
}
// Create
message Create<Resource>Request {
string title = 1 [(buf.validate.field).string = {min_len: 1, max_len: 255}];
string body = 2 [(buf.validate.field).string.min_len = 1];
<Resource>Status status = 3 [(buf.validate.field).enum = {defined_only: true, not_in: [0]}];
repeated string tags = 4;
}
message Create<Resource>Response {
<Resource> <resource> = 1;
}
// Get
message Get<Resource>Request {
string id = 1 [(buf.validate.field).string.uuid = true];
}
message Get<Resource>Response {
<Resource> <resource> = 1;
}
// List
message List<Resource>Request {
int32 page_size = 1 [(buf.validate.field).int32 = {gte: 0, lte: 100}];
string page_token = 2;
}
message List<Resource>Response {
repeated <Resource> items = 1;
string next_page_token = 2;
}
// Update
message Update<Resource>Request {
string id = 1 [(buf.validate.field).string.uuid = true];
<Resource> <resource> = 2 [(buf.validate.field).required = true];
google.protobuf.FieldMask update_mask = 3 [(buf.validate.field).required = true];
}
message Update<Resource>Response {
<Resource> <resource> = 1;
}
// Delete
message Delete<Resource>Request {
string id = 1 [(buf.validate.field).string.uuid = true];
}
Conventions:
Create<Resource>, Get<Resource>, List<Resource>, Update<Resource>, Delete<Resource><RpcName>Request and <RpcName>Response message pair — except Delete, which returns google.protobuf.Empty (errors use gRPC status codes)Create<Resource>Response { <Resource> <resource> = 1; })string id fields (not Ref types)items for consistency across all entities[(buf.validate.field).string.uuid = true]{min_len: 1, max_len: 255}{defined_only: true, not_in: [0]}page_size with range validation {gte: 0, lte: 100} + page_token — 0 means "use server default" (AIP-158)google.protobuf.FieldMask to specify which fields to update<resource> and update_mask are both requiredSpaceRef) to express ownership; Get/List/Update/Delete operate by ID alone — the API does not assume access scoping by parentprotos/buf.yamlLives at the protobuf root (not per-domain). Create only if it doesn't already exist.
version: v2
deps:
- buf.build/bufbuild/protovalidate
make codegen to generate Go code from protomake vet — should pass (no new Go source files reference gen/ yet)_model.proto, _refs.proto, _service.proto<domain>.v1 under protos/<domain>/v1/go_package uses alias format: <domain>/v1;<domain>v1_UNSPECIFIED = 0 zero valuegoogle.protobuf.Timestampgoogle.protobuf.FieldMask[(buf.validate.field).string.uuid = true]{defined_only: true, not_in: [0]}buf/validate annotations (enforced via FieldMask updates)page_size with {gte: 0, lte: 100} + page_token / next_page_tokenstring id)itemsgoogle.protobuf.Empty (not a response with bool success)protos/buf.yaml present at protobuf root with protovalidate dependencymake codegen succeedstesting
Review a test PR
tools
Review a search indexing PR
tools
Review a scaffold PR
tools
Review a proto PR