This document outlines development practices required when contributing to the zepctl codebase.
- Language: Go 1.25+
- CLI Framework: Cobra with Viper for configuration
- SDK: github.com/getzep/zep-go/v3
make build # Build for current platform
make test # Run tests with race detection
make lint # Run golangci-lint
make fmt # Format code with gofumpt
make fmt-check # Verify formatting without changes
make tidy # Tidy go.mod dependenciesAlways run make lint before committing changes.
- Use
gofumptfor formatting (stricter thangofmt) - Run
make fmtto format all files - CI will fail if code is not properly formatted
The project uses golangci-lint v2 with the following enabled linters:
bodyclose,copyloopvar,dogsled,errorlintgoconst,gocritic,gocyclo,godot,gosecmisspell,nakedret,noctx,nolintlintrevive,staticcheck,unconvert,unparam,whitespace
Key linter settings:
- goconst: Minimum 8 occurrences before suggesting a constant
- gocyclo: Maximum complexity of 15
- nolintlint: Requires explanation and specific linter name for
//nolintdirectives
- Always wrap errors with context:
fmt.Errorf("operation: %w", err) - Use the
errorlintpatterns for error comparisons - Check type assertions:
value, ok := x.(Type)
- Follow Go standard naming conventions
- Use
IDnotIdfor identifiers (e.g.,UserID,GraphID) - Use
UUIDnotUuidfor UUIDs - Use
URLnotUrlfor URLs - Use
APInotApifor API references
cmd/zepctl/ # Main entry point
internal/
cli/ # Cobra command implementations
client/ # Zep API client wrapper
config/ # Configuration management
output/ # Output formatting (table, JSON, YAML)
docs/ # Documentation files
- Create a new file in
internal/cli/(e.g.,resource.go) - Define the command hierarchy:
var resourceCmd = &cobra.Command{ Use: "resource", Short: "Manage resources", Long: `Detailed description of resource management.`, }
- Register commands in
init():func init() { rootCmd.AddCommand(resourceCmd) resourceCmd.AddCommand(resourceListCmd) // Add flags resourceListCmd.Flags().Int("limit", 50, "Maximum results") }
- Follow existing patterns for table output, error handling, and confirmation prompts
The Zep SDK uses cursor-based pagination:
Limit *int- Maximum items to returnUUIDCursor *string- UUID of last item from previous page
Use zep.Int() and zep.String() helpers for pointer fields:
req := &zep.GraphNodesRequest{
Limit: zep.Int(limit),
UUIDCursor: zep.String(cursor),
}When implementing exclusion filters, use the correct SDK fields:
ExcludeNodeLabels(notNodeLabels) for--exclude-node-labelsflagExcludeEdgeTypes(notEdgeTypes) for--exclude-edge-typesflagMinScoreandMinFactRatingwere removed in SDK v3.17.0; the CLI no longer exposes these flags
Support multiple output formats via the output package:
if output.GetFormat() == output.FormatTable {
tbl := output.NewTable("COLUMN1", "COLUMN2")
tbl.WriteHeader()
tbl.WriteRow(value1, value2)
return tbl.Flush()
}
return output.Print(data) // JSON/YAML outputFor destructive operations, require confirmation unless --force is provided:
if !force {
fmt.Printf("Delete %q? [y/N]: ", name)
reader := bufio.NewReader(os.Stdin)
response, _ := reader.ReadString('\n')
response = strings.TrimSpace(strings.ToLower(response))
if response != "y" && response != "yes" {
output.Info("Aborted")
return nil
}
}For sensitive data like API keys, use golang.org/x/term for masked input:
if term.IsTerminal(int(os.Stdin.Fd())) {
keyBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Println() // newline after hidden input
// ...
}Implement mutual exclusion and dependency validation early in command handlers:
if sourceUser != "" && sourceGraph != "" {
return fmt.Errorf("--source-user and --source-graph are mutually exclusive")
}
if sourceUser != "" && targetGraph != "" {
return fmt.Errorf("--target-graph cannot be used with --source-user")
}- Update
docs/cli.mdxwhen adding or modifying commands - Include usage examples in command
Longdescriptions - Document all flags with clear descriptions
- CI (
ci.yml): Runs lint, test, and build on PRs - Release (
release.yml): Creates releases via GoReleaser on tags
Releases are built for:
- Linux (amd64, arm64)
- macOS (arm64)
Homebrew tap is published to getzep/homebrew-tap.
Run tests with race detection:
make testTests should:
- Use table-driven test patterns
- Mock external API calls
- Avoid hardcoded test data that could become stale