CLI Command Patterns and Conventions
May 1, 2026 · View on GitHub
This document provides guidance for developing CLI commands in GitHub Agentic Workflows. It covers command structure, naming conventions, flag patterns, error handling, testing requirements, and help text standards.
Table of Contents
- Command Structure
- Naming Conventions
- Flag Patterns
- Error Handling
- Console Output
- Testing Requirements
- Help Text Standards
- Anti-Patterns
- Examples
Command Structure
Standard File Structure
Every command should follow this standard structure:
package cli
import (
// Standard library imports
"fmt"
"os"
// Internal imports
"github.com/github/gh-aw/pkg/console"
"github.com/github/gh-aw/pkg/logger"
// External imports
"github.com/spf13/cobra"
)
// Logger instance with namespace following cli:command_name pattern
var commandLog = logger.New("cli:command_name")
// NewCommandNameCommand creates the command-name command
func NewCommandNameCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "command-name [args]",
Short: "Brief one-line description under 80 chars",
Long: `Detailed description with examples...`,
Args: cobra.ExactArgs(1), // or other validator
RunE: func(cmd *cobra.Command, args []string) error {
// Parse flags
flagValue, _ := cmd.Flags().GetString("flag-name")
verbose, _ := cmd.Flags().GetBool("verbose")
// Call main function
return RunCommandName(args[0], flagValue, verbose)
},
}
// Add flags
cmd.Flags().StringP("flag-name", "f", "default", "Flag description")
addVerboseFlag(cmd)
// Register completions
RegisterDirFlagCompletion(cmd, "output")
return cmd
}
// RunCommandName executes the command logic
func RunCommandName(arg string, flagValue string, verbose bool) error {
commandLog.Printf("Starting command: arg=%s, flagValue=%s", arg, flagValue)
// Validate inputs early
if err := validateInputs(arg, flagValue); err != nil {
return err
}
// Execute command logic
result, err := executeCommand(arg, flagValue)
if err != nil {
return fmt.Errorf("failed to execute command: %w", err)
}
// Output results
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(result))
commandLog.Print("Command completed successfully")
return nil
}
// Internal implementation functions
func validateInputs(arg string, flagValue string) error {
if arg == "" {
return fmt.Errorf("argument cannot be empty")
}
return nil
}
func executeCommand(arg string, flagValue string) (string, error) {
// Implementation
return "Success", nil
}
Key Structure Elements
- Logger Namespace: Always create a logger with format
cli:command_name - Public API: Two exported functions:
NewXCommand() *cobra.Command- Creates the commandRunX(...) error- Executes command logic (testable)
- Internal Functions: Private helper functions for implementation
- Flag Parsing: Parse flags in
RunEfunction - Early Validation: Validate all inputs before processing
- Error Wrapping: Use
fmt.Errorfwith%wfor context
File Organization
Commands can be organized in multiple ways depending on complexity:
Single File Pattern (Simple Commands)
For commands with < 500 lines:
command_name.go- All command logiccommand_name_test.go- All tests
Example: status_command.go (368 lines)
Multi-File Pattern (Complex Commands)
For commands with > 500 lines, split into focused files:
command_name_command.go- Command definitioncommand_name_config.go- Configuration typescommand_name_helpers.go- Utility functionscommand_name_validation.go- Validation logiccommand_name_orchestrator.go- Main orchestration
Example: Compile command split into multiple files:
compile_config.go- Configuration typescompile_validation.go- Validation logiccompile_orchestrator.go- Main orchestration (entry point)
Naming Conventions
File Names
| File Type | Pattern | Example |
|---|---|---|
| Command file | *_command.go | audit_command.go |
| Test file | *_command_test.go | audit_command_test.go |
| Integration test | *_integration_test.go | compile_integration_test.go |
| Helper functions | *_helpers.go | compile_helpers.go |
| Configuration | *_config.go | compile_config.go |
Logger Namespaces
Format: cli:command_name
// ✅ CORRECT
var auditLog = logger.New("cli:audit")
var compileLog = logger.New("cli:compile_orchestrator")
var statusLog = logger.New("cli:status_command")
// ❌ INCORRECT
var log = logger.New("audit") // Missing cli: prefix
var logger = logger.New("compile") // Missing cli: prefix, conflicts with package
Public Functions
| Function Type | Pattern | Example |
|---|---|---|
| Command creator | NewXCommand() | NewAuditCommand() |
| Command runner | RunX(...) | RunAuditWorkflowRun(...) |
| Helper functions | Descriptive names | validateInputs(), fetchWorkflows() |
Configuration Structs
Configuration struct names should end with Config:
// ✅ CORRECT
type CompileConfig struct {
WorkflowFile string
OutputDir string
Verbose bool
}
type AuditConfig struct {
RunID int64
Verbose bool
}
// ❌ INCORRECT
type CompileOptions struct { ... } // Use Config suffix
type AuditParams struct { ... } // Use Config suffix
Flag Patterns
Global Flags
Global flags are available to all commands:
// Defined in commands.go
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output")
rootCmd.PersistentFlags().BoolVar(&noBanner, "no-banner", false, "Disable banner messages")
Common Flags
Common flags with helper functions (defined in flags.go):
// Engine flag - Override AI engine
addEngineFlag(cmd) // --engine/-e
addEngineFilterFlag(cmd) // --engine/-e for filtering
// Repository flag
addRepoFlag(cmd) // --repo/-r
// Output directory flag
addOutputFlag(cmd, defaultDir) // --output/-o
// JSON output flag
addJSONFlag(cmd) // --json/-j
Standard Short Flags
Reserve these short flags for consistent meanings:
| Short Flag | Meaning | Long Flag |
|---|---|---|
-v | Verbose output | --verbose |
-e | Engine selection/filter | --engine |
-r | Repository | --repo |
-o | Output directory | --output |
-j | JSON output | --json |
-f | Force/file | --force/--file |
-w | Watch mode | --watch |
Flag Naming
- Use kebab-case for flag names:
--output-dir,--run-id - Use descriptive names that indicate purpose with noun phrases and conventional prefixes
- Avoid abbreviations unless universally understood
Flag Validation
Validate flags early in the RunE function:
RunE: func(cmd *cobra.Command, args []string) error {
// Parse flags
engine, _ := cmd.Flags().GetString("engine")
runID, _ := cmd.Flags().GetInt64("run-id")
// Validate flags
if engine != "" && !isValidEngine(engine) {
return fmt.Errorf("invalid engine: %s (must be copilot, claude, codex, or custom)", engine)
}
if runID <= 0 {
return fmt.Errorf("run-id must be positive")
}
// Continue with validated values
return RunCommand(engine, runID)
},
Flag Completion
Register flag completions for better UX:
// Directory completion
RegisterDirFlagCompletion(cmd, "output")
// File completion
RegisterFileFlagCompletion(cmd, "file", "*.md")
// Custom completion
cmd.RegisterFlagCompletionFunc("engine", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"copilot", "claude", "codex", "custom"}, cobra.ShellCompDirectiveNoFileComp
})
Error Handling
User-Facing Errors
Always use console.FormatErrorMessage() for user-facing errors:
// ✅ CORRECT - Formatted error message
if err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
return err
}
// ❌ INCORRECT - Plain error message
if err != nil {
fmt.Fprintln(os.Stderr, err)
return err
}
// ❌ INCORRECT - Writing to stdout
if err != nil {
fmt.Println(err) // Should use stderr
return err
}
Error Wrapping
Use fmt.Errorf with %w to provide context:
// ✅ CORRECT - Error wrapping with context
result, err := fetchData(url)
if err != nil {
return fmt.Errorf("failed to fetch data from %s: %w", url, err)
}
// ❌ INCORRECT - No context
result, err := fetchData(url)
if err != nil {
return err
}
Actionable Error Messages
Provide clear, actionable error messages:
// ✅ CORRECT - Actionable error with suggestion
if !workflowExists {
return fmt.Errorf("workflow '%s' not found. Use 'gh aw list' to see available workflows", name)
}
// ✅ CORRECT - Validation error with expected format
if !strings.Contains(input, "=") {
return fmt.Errorf("invalid input format '%s': expected key=value", input)
}
// ❌ INCORRECT - Vague error message
if !workflowExists {
return fmt.Errorf("not found")
}
Error Logging
Log errors with context before returning:
func RunCommand(arg string) error {
commandLog.Printf("Starting command with arg: %s", arg)
result, err := processArg(arg)
if err != nil {
commandLog.Printf("Failed to process arg: %v", err)
return fmt.Errorf("failed to process argument: %w", err)
}
commandLog.Print("Command completed successfully")
return nil
}
Console Output
Output Guidelines
- ALL output goes to stderr:
fmt.Fprintln(os.Stderr, ...) - Use console formatting: Never use plain
fmt.Println() - Consistent styling: Use appropriate message types
Message Types
import "github.com/github/gh-aw/pkg/console"
// Success messages
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Workflow compiled successfully"))
// Info messages
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Fetching workflow status..."))
// Warning messages
fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Workflow has unstaged changes"))
// Error messages
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
// Command messages (CLI commands being executed)
fmt.Fprintln(os.Stderr, console.FormatCommandMessage("gh workflow run workflow.yml"))
// Progress messages (operations in progress)
fmt.Fprintln(os.Stderr, console.FormatProgressMessage("Downloading artifacts..."))
// Prompt messages (user prompts)
fmt.Fprintln(os.Stderr, console.FormatPromptMessage("Select a workflow:"))
// Count messages (numeric counts)
fmt.Fprintln(os.Stderr, console.FormatCountMessage(fmt.Sprintf("Found %d workflows", count)))
// Verbose messages (debug info, only shown with --verbose)
if verbose {
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Detailed debug information"))
}
// Location messages (file paths, URLs)
fmt.Fprintln(os.Stderr, console.FormatLocationMessage(filepath))
Structured Output
For structured data display, use console.RenderStruct():
type WorkflowStatus struct {
Workflow string `json:"workflow" console:"header:Workflow"`
Engine string `json:"engine" console:"header:Engine"`
Compiled string `json:"compiled" console:"header:Compiled"`
Status string `json:"status" console:"header:Status"`
}
statuses := []WorkflowStatus{...}
// Render table
fmt.Print(console.RenderStruct(statuses))
JSON Output
Support JSON output with --json flag:
if jsonOutput {
jsonBytes, err := json.MarshalIndent(data, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal JSON: %w", err)
}
fmt.Println(string(jsonBytes)) // JSON goes to stdout
return nil
}
// Normal output goes to stderr
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Operation complete"))
Verbose Output
Verbose messages should provide additional detail:
if verbose {
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(
fmt.Sprintf("Processing workflow: %s", workflowName)))
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(
fmt.Sprintf("Engine: %s", engine)))
}
Testing Requirements
Test File Structure
Every command must have corresponding test files:
command_name_command.go
command_name_command_test.go # Unit tests
command_name_integration_test.go # Integration tests (optional)
Table-Driven Tests
Use table-driven tests for testing multiple scenarios:
func TestRunCommand(t *testing.T) {
tests := []struct {
name string
input string
expected string
shouldErr bool
}{
{
name: "valid input",
input: "test-workflow",
expected: "Success",
shouldErr: false,
},
{
name: "empty input",
input: "",
expected: "",
shouldErr: true,
},
{
name: "invalid format",
input: "invalid@workflow",
expected: "",
shouldErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := RunCommand(tt.input)
if tt.shouldErr {
assert.Error(t, err, "Expected error for input: %s", tt.input)
} else {
assert.NoError(t, err, "Should not error for input: %s", tt.input)
assert.Equal(t, tt.expected, result, "Incorrect result for input: %s", tt.input)
}
})
}
}
Assert vs Require
Use testify assertions appropriately:
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestWorkflowCompilation(t *testing.T) {
// Use require for critical setup (stops test if fails)
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.md")
err := os.WriteFile(testFile, []byte(content), 0644)
require.NoError(t, err, "Failed to write test file")
// Use assert for test validations (continues checking)
result, err := CompileWorkflow(testFile)
assert.NoError(t, err, "Should compile valid workflow")
assert.NotNil(t, result, "Result should not be nil")
assert.Equal(t, "expected", result.Field, "Field value incorrect")
}
Test Coverage
Tests should cover:
- Valid inputs: Normal operation with valid data
- Invalid inputs: Error handling with invalid data
- Edge cases: Empty strings, nil values, boundary conditions
- Flag handling: All flag combinations
- Error paths: All error conditions
Test Naming
Use descriptive test names:
// ✅ CORRECT - Descriptive test names
func TestAuditCommand_ValidRunID(t *testing.T) { ... }
func TestAuditCommand_InvalidRunID(t *testing.T) { ... }
func TestAuditCommand_MissingArtifacts(t *testing.T) { ... }
// ❌ INCORRECT - Vague test names
func TestAudit1(t *testing.T) { ... }
func TestAudit2(t *testing.T) { ... }
Help Text Standards
Short Description
- Under 80 characters
- Action-oriented (start with verb)
- No period at the end
- Clear and concise
// ✅ CORRECT
Short: "Investigate an agentic workflow run and generate a detailed report"
Short: "Show status of agentic workflow files"
Short: "Compile Markdown workflows to GitHub Actions YAML"
// ❌ INCORRECT
Short: "This command investigates workflows." // Too wordy, has period
Short: "Status" // Not descriptive enough
Short: "A very long description that goes well over the eighty character limit for short descriptions" // Too long
Long Description
Structure long descriptions with:
- Overview: What the command does (1-2 sentences)
- Context: When to use it, what it's for
- Details: Specific behavior, options, features
- Examples: Practical usage examples (minimum 3)
Long: `Audit a single workflow run by downloading artifacts and logs, detecting errors,
analyzing MCP tool usage, and generating a concise Markdown report suitable for AI agents.
This command accepts:
- A numeric run ID (e.g., 1234567890)
- A GitHub Actions run URL (e.g., https://github.com/owner/repo/actions/runs/1234567890)
- A GitHub Actions job URL (e.g., https://github.com/owner/repo/actions/runs/1234567890/job/9876543210)
This command:
- Downloads artifacts and logs for the specified run ID
- Detects errors and warnings in the logs
- Analyzes MCP tool usage statistics
- Generates a concise Markdown report
Examples:
gh aw audit 1234567890 # Audit run with ID 1234567890
gh aw audit https://... # Audit from run URL
gh aw audit 1234567890 -o ./reports # Custom output directory
gh aw audit 1234567890 -v # Verbose output
gh aw audit 1234567890 --parse # Parse agent and firewall logs`,
Example Guidelines
Provide at least 3 practical examples:
- Basic usage: Simplest form of the command
- Common options: With frequently used flags
- Advanced usage: Complex scenarios
Examples:
gh aw compile workflow.md # Compile single workflow
gh aw compile workflow.md -v # Verbose output
gh aw compile -d .github/workflows # Compile directory
gh aw compile workflow.md --watch # Watch mode
gh aw compile --all # Compile all workflows
Including WorkflowIDExplanation
For commands that accept workflow IDs, include the standard explanation:
import "github.com/github/gh-aw/pkg/cli"
Long: `Description of command...
` + cli.WorkflowIDExplanation + `
Examples:
...`,
Anti-Patterns
What NOT to Do
1. Don't Write to Stdout (Except JSON)
// ❌ INCORRECT
fmt.Println("Success")
fmt.Printf("Status: %s\n", status)
// ✅ CORRECT
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Success"))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Status: %s", status)))
2. Don't Use Plain Error Messages
// ❌ INCORRECT
fmt.Fprintln(os.Stderr, err)
// ✅ CORRECT
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
3. Don't Skip Error Context
// ❌ INCORRECT
if err != nil {
return err
}
// ✅ CORRECT
if err != nil {
return fmt.Errorf("failed to parse workflow: %w", err)
}
4. Don't Create Monolithic Command Files
// ❌ INCORRECT - Single file with 2000+ lines
compile_orchestrator.go // 2000 lines
// ✅ CORRECT - Split into focused files
compile_config.go // Configuration (100 lines)
compile_validation.go // Validation (300 lines)
compile_orchestrator.go // Main logic (500 lines)
5. Don't Use Inconsistent Logger Namespaces
// ❌ INCORRECT
var log = logger.New("audit")
var logger = logger.New("compile")
// ✅ CORRECT
var auditLog = logger.New("cli:audit")
var compileLog = logger.New("cli:compile_orchestrator")
6. Don't Skip Input Validation
// ❌ INCORRECT
func RunCommand(arg string) error {
// Directly use arg without validation
result := processArg(arg)
return nil
}
// ✅ CORRECT
func RunCommand(arg string) error {
if arg == "" {
return fmt.Errorf("argument cannot be empty")
}
if !isValid(arg) {
return fmt.Errorf("invalid argument format: %s", arg)
}
result := processArg(arg)
return nil
}
7. Don't Forget Flag Completions
// ❌ INCORRECT - No completions
cmd.Flags().StringP("output", "o", "", "Output directory")
// ✅ CORRECT - With completions
cmd.Flags().StringP("output", "o", "", "Output directory")
RegisterDirFlagCompletion(cmd, "output")
8. Don't Create Vague Help Text
// ❌ INCORRECT
Short: "Compile files"
Long: `This compiles files.`
// ✅ CORRECT
Short: "Compile Markdown workflows to GitHub Actions YAML"
Long: `Compile agentic workflow Markdown files to GitHub Actions YAML with validation.
This command:
- Parses Markdown frontmatter and content
- Validates workflow configuration
- Generates GitHub Actions YAML
- Supports watch mode for automatic recompilation
Examples:
gh aw compile workflow.md
gh aw compile --all
gh aw compile workflow.md --watch`,
Examples
Complete Command Example
Here's a complete example following all patterns:
package cli
import (
"fmt"
"os"
"path/filepath"
"github.com/github/gh-aw/pkg/console"
"github.com/github/gh-aw/pkg/logger"
"github.com/spf13/cobra"
)
var validateLog = logger.New("cli:validate_command")
// NewValidateCommand creates the validate command
func NewValidateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "validate <workflow-file>",
Short: "Validate agentic workflow Markdown files",
Long: `Validate agentic workflow Markdown files for correctness and compatibility.
This command:
- Validates frontmatter syntax and required fields
- Checks GitHub Actions compatibility
- Validates tool configurations
- Reports errors and warnings with actionable suggestions
` + WorkflowIDExplanation + `
Examples:
gh aw validate workflow.md # Validate single workflow
gh aw validate workflow.md -v # Verbose output
gh aw validate --all # Validate all workflows
gh aw validate workflow.md --strict # Strict mode validation`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
verbose, _ := cmd.Flags().GetBool("verbose")
strict, _ := cmd.Flags().GetBool("strict")
all, _ := cmd.Flags().GetBool("all")
var workflowFile string
if len(args) > 0 {
workflowFile = args[0]
}
return RunValidate(workflowFile, all, strict, verbose)
},
}
cmd.Flags().Bool("all", false, "Validate all workflow files")
cmd.Flags().Bool("strict", false, "Enable strict mode validation")
return cmd
}
// RunValidate validates workflow files
func RunValidate(workflowFile string, all bool, strict bool, verbose bool) error {
validateLog.Printf("Starting validation: file=%s, all=%v, strict=%v", workflowFile, all, strict)
// Validate inputs
if !all && workflowFile == "" {
return fmt.Errorf("workflow file required (or use --all to validate all workflows)")
}
if all && workflowFile != "" {
return fmt.Errorf("cannot specify both workflow file and --all flag")
}
// Get files to validate
var files []string
if all {
var err error
files, err = findAllWorkflowFiles()
if err != nil {
validateLog.Printf("Failed to find workflow files: %v", err)
return fmt.Errorf("failed to find workflow files: %w", err)
}
} else {
files = []string{workflowFile}
}
if verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(
fmt.Sprintf("Validating %d workflow file(s)...", len(files))))
}
// Validate each file
var hasErrors bool
for _, file := range files {
if err := validateWorkflowFile(file, strict, verbose); err != nil {
hasErrors = true
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(
fmt.Sprintf("%s: %s", filepath.Base(file), err.Error())))
} else {
if verbose {
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(
fmt.Sprintf("%s: Valid", filepath.Base(file))))
}
}
}
if hasErrors {
return fmt.Errorf("validation failed for one or more workflows")
}
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(
fmt.Sprintf("All %d workflow(s) validated successfully", len(files))))
validateLog.Print("Validation completed successfully")
return nil
}
func findAllWorkflowFiles() ([]string, error) {
pattern := filepath.Join(".github", "workflows", "*.md")
return filepath.Glob(pattern)
}
func validateWorkflowFile(file string, strict bool, verbose bool) error {
if verbose {
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(
fmt.Sprintf("Validating %s...", file)))
}
// Validation logic here
return nil
}
Test Example
package cli
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRunValidate(t *testing.T) {
tests := []struct {
name string
workflowFile string
all bool
strict bool
shouldErr bool
errorMsg string
}{
{
name: "valid workflow file",
workflowFile: "test-workflow.md",
all: false,
strict: false,
shouldErr: false,
},
{
name: "no file and no all flag",
workflowFile: "",
all: false,
strict: false,
shouldErr: true,
errorMsg: "workflow file required",
},
{
name: "both file and all flag",
workflowFile: "test-workflow.md",
all: true,
strict: false,
shouldErr: true,
errorMsg: "cannot specify both",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup test environment
tmpDir := t.TempDir()
originalDir, err := os.Getwd()
require.NoError(t, err)
defer os.Chdir(originalDir)
err = os.Chdir(tmpDir)
require.NoError(t, err)
// Create test workflow file if specified
if tt.workflowFile != "" {
workflowsDir := filepath.Join(".github", "workflows")
err := os.MkdirAll(workflowsDir, 0755)
require.NoError(t, err)
testFile := filepath.Join(workflowsDir, tt.workflowFile)
err = os.WriteFile(testFile, []byte("---\nengine: copilot\n---\n# Test"), 0644)
require.NoError(t, err)
}
// Run validation
err = RunValidate(tt.workflowFile, tt.all, tt.strict, false)
// Assert results
if tt.shouldErr {
assert.Error(t, err, "Expected error for test: %s", tt.name)
if tt.errorMsg != "" {
assert.Contains(t, err.Error(), tt.errorMsg,
"Error message should contain expected text")
}
} else {
assert.NoError(t, err, "Should not error for test: %s", tt.name)
}
})
}
}
func TestValidateCommand_FlagParsing(t *testing.T) {
cmd := NewValidateCommand()
// Test flag parsing
cmd.SetArgs([]string{"workflow.md", "--strict", "--verbose"})
err := cmd.ParseFlags([]string{"workflow.md", "--strict", "--verbose"})
assert.NoError(t, err, "Should parse flags successfully")
strict, _ := cmd.Flags().GetBool("strict")
assert.True(t, strict, "Strict flag should be true")
verbose, _ := cmd.Flags().GetBool("verbose")
assert.True(t, verbose, "Verbose flag should be true")
}
Command Development Checklist
Use this checklist when developing a new command:
- File created with
*_command.gosuffix - Logger created with
cli:command_namenamespace -
NewXCommand()function defined -
RunX()function defined (testable) - Short description under 80 chars
- Long description with context and examples
- Minimum 3 usage examples provided
- Flags added with appropriate short flags
- Flag completions registered
- Input validation implemented
- Error messages use
console.FormatErrorMessage() - Success messages use
console.FormatSuccessMessage() - All output goes to stderr (except JSON)
- JSON output supported if applicable
- Test file created (
*_command_test.go) - Table-driven tests implemented
- Valid input tests added
- Invalid input tests added
- Edge case tests added
- Flag handling tests added
- Documentation updated if needed
Related Documentation
- Testing Framework: See
scratchpad/testing.mdfor testing guidelines - Console Rendering: See
skills/console-rendering/SKILL.mdfor console output details - Error Messages: See
skills/error-messages/SKILL.mdfor error message style guide - Code Organization: See
scratchpad/code-organization.mdfor file organization patterns - Breaking Changes: See
scratchpad/breaking-cli-rules.mdfor breaking change guidelines
Last Updated: 2026-01-01