devkit is a Go CLI toolkit for DevOps automation. It ships as a single static
binary and exposes three subcommands: scaffold, health, and report.
These rules govern how all contributors write, structure, and test code in
this repository.
devkit/
├── main.go ← calls cmd.Execute() only — no logic
├── go.mod
├── go.sum
├── Makefile
├── CLAUDE.md
├── README.md
├── .golangci.yml
├── .gosec.yaml
├── .goreleaser.yml
├── cmd/
│ ├── root.go ← owned by scaffold lead — defines rootCmd and Execute()
│ ├── scaffold.go ← scaffold subcommand registration and flag binding
│ ├── health.go ← health subcommand registration and flag binding
│ └── report.go ← report subcommand registration and flag binding
├── internal/
│ ├── scaffold/
│ │ ├── scaffold.go
│ │ └── scaffold_test.go
│ ├── health/
│ │ ├── health.go
│ │ └── health_test.go
│ └── report/
│ ├── report.go
│ └── report_test.go
├── docs/
│ └── specs/
│ ├── SPEC_scaffold.md
│ ├── SPEC_health.md
│ └── SPEC_report.md
└── website/
└── devkit-landing.html
| File | Owner | Rule |
|---|---|---|
main.go |
scaffold lead | Nobody else touches this |
cmd/root.go |
scaffold lead | Nobody else touches this |
cmd/scaffold.go |
scaffold lead | |
cmd/health.go |
health dev | |
cmd/report.go |
report dev | |
internal/scaffold/* |
scaffold lead | |
internal/health/* |
health dev | |
internal/report/* |
report dev | |
Makefile |
scaffold lead | New targets only — never modify existing |
.golangci.yml |
scaffold lead | |
go.mod / go.sum |
all | Run go mod tidy before every PR |
main.go must contain exactly this and nothing more:
package main
import "devkit/cmd"
func main() {
cmd.Execute()
}No cobra imports. No flag parsing. No logic. If you feel the urge to add
something to main.go, it belongs in cmd/root.go or an internal/ package.
- Every
cmd/*.gofile (exceptroot.go) must self-register usinginit():func init() { rootCmd.AddCommand(myCmd) }
rootCmd.AddCommand()is never called frommain.goorroot.go- All flags are registered in
init(), not inRunE - Command files contain: var declaration,
init(), andRunEonly RunEreads flags, builds an Options struct, callsinternal/<pkg>.Run(), and handles the returned error — nothing else- Use
RunE(notRun) on all commands so errors propagate cleanly
- All business logic lives in
internal/— never incmd/ - Each package exposes a single
Run(opts Options) errorentry point Optionsis a plain struct with exported fields matching the CLI flags- No
os.Exit()ininternal/— return errors, letcmd/handle exit - No
fmt.Printlnininternal/for user-facing output — usefmt.Fprintf(w io.Writer, ...)with a writer passed via Options, or write toos.Stdoutexplicitly when it is truly always stdout internal/packages must not import fromcmd/
make verify— minimum gate before every commit:vet + lint + testmake qa— full gate before every PR: all analysis + tests + coverage + securitymake ci— same asqaplus release validation — used in GitHub Actions- Never add
.PHONYtargets that shell out to scripts outside the Makefile - Tab-indent all Makefile recipe lines — never spaces
- Add no runtime dependencies without team discussion
- Dev tools (linters, security scanners) are installed via
go install— they do not appear ingo.mod go mod tidymust pass cleanly before any merge tomain- Check
make vulnoutput before adding any new dependency
- Every error must be handled — no blank identifier:
_ = someFunc() - Wrap errors with context:
fmt.Errorf("scaffold: create dir: %w", err) - Return errors up the call stack — do not swallow them with a log line
os.Exit()is only called incmd/layer after printing toos.Stderr- Never use
panicexcept for programming errors that should never occur in production (e.g., nil pointer from a required dependency)
- Test files use the external package convention:
package scaffold_test - Table-driven tests are preferred for functions with multiple input cases
- Test function names describe behavior:
TestRunReturnsErrorOnEmptyName - Use
t.Skip("not yet implemented")for stub tests — never empty bodies - Coverage threshold: 60% minimum enforced by
make coverage - Race detector is always on:
go test -race ./... - No
time.Sleepin tests — use channels or sync primitives
- Do NOT run git commands of any kind
- Do not git add, git commit, git push, or git stash
- After completing a task, list the files changed and stop
- The developer reviews the diff and commits manually