Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/adr/38946-use-cobra-example-field-for-command-examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# ADR-38946: Use Cobra's `Example:` Field for Command Usage Examples

**Date**: 2026-06-12
**Status**: Draft

## Context

The CLI in `pkg/cli/` defines ~36 cobra commands, and historically every command embedded its usage examples as hand-written prose inside the `Long:` description field under an `Examples:` heading. Cobra provides a dedicated `Example:` field that is rendered separately in `--help` output and, critically, is consumed by `cobra/doc.GenMarkdownTree()` for documentation generation. Because the examples lived inside `Long:`, generated documentation could not surface them in the expected structured location, and the examples were not addressable for testing or tooling. This PR migrates all command definitions to use the native `Example:` field. The change is large by line count but mechanical and repetitive (moving example blocks between two struct fields).

## Decision

We will store all cobra command usage examples in the native cobra `Example:` field rather than embedding them as `Examples:` prose inside the `Long:` field. Non-example prose that previously followed the `Examples:` section (e.g., "Workflow specifications:", "The command will:") is moved back into `Long:` where it belongs. Tests that previously asserted example strings appeared in `cmd.Long` are updated to assert against `cmd.Example`. This aligns the codebase with cobra's intended API contract, restores correct `GenMarkdownTree()` documentation output, and makes examples independently inspectable.

## Alternatives Considered

### Alternative 1: Keep examples in `Long:` prose

Leave the existing convention untouched and continue embedding examples under an `Examples:` heading inside `Long:`. Rejected because it breaks `cobra/doc.GenMarkdownTree()` documentation generation, keeps examples untestable as a distinct field, and diverges from cobra's documented usage of the `Example:` field.

### Alternative 2: Custom help template / renderer

Introduce a custom cobra help/usage template that parses the `Examples:` section out of `Long:` at render time. Rejected as unnecessary complexity: it reimplements behavior cobra already provides natively, adds a maintenance burden, and still would not feed `GenMarkdownTree()` correctly.

## Consequences

### Positive
- `cobra/doc.GenMarkdownTree()` produces correct documentation with examples in their structured location.
- Examples become independently testable and inspectable via `cmd.Example`.
- Command definitions follow cobra's intended API convention, improving consistency across all ~36 commands.

### Negative
- Large, repetitive diff (36 command files plus 8 secondary prose fixes and 5 test updates) that touches many files at once and must be reviewed for correctness.
- A standing convention contributors must remember to follow for new commands; without a lint check, regressions can reappear.

### Neutral
- `--help` output formatting shifts slightly because the `Example:` field is rendered under cobra's own "Examples:" section heading rather than inline within the long description.
- Test assertions referencing example content move from `cmd.Long` to `cmd.Example`.

---

*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/27447688258) workflow. The PR author must review, complete, and finalize this document before the PR can merge.*
27 changes: 13 additions & 14 deletions pkg/cli/add_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,6 @@ func NewAddCommand(validateEngine func(string) error) *cobra.Command {
This command adds workflows directly without interactive prompts. Use 'add-wizard'
for a guided setup that configures secrets, creates a pull request, and more.

Examples:
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/daily-repo-status # Add workflow directly
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/repo-assist # Add package from repository root aw.yml
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/packages/repo-assist # Add package from nested aw.yml
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/ci-doctor@v1.0.0 # Add with version
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/workflows/ci-doctor.md@main
` + string(constants.CLIExtensionPrefix) + ` add https://github.com/githubnext/agentics/blob/main/workflows/ci-doctor.md
` + string(constants.CLIExtensionPrefix) + ` add https://example.com/my-workflow.md # Add workflow from any HTTPS URL
` + string(constants.CLIExtensionPrefix) + ` add https://example.com/workflow.json # Import JSON workflow definition
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/ci-doctor --create-pull-request --force
` + string(constants.CLIExtensionPrefix) + ` add ./my-workflow.md # Add local workflow
` + string(constants.CLIExtensionPrefix) + ` add ./*.md # Add all local workflows
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/ci-doctor --dir .github/workflows/shared # Add to .github/workflows/shared/

Workflow specifications:
- Two parts: "owner/repo[@version]" (loads repository-root aw.yml package)
- Three+ parts without .md: "owner/repo/folder[@version]" (loads nested aw.yml package when present)
Expand All @@ -96,6 +82,19 @@ Note: In GitHub Enterprise repos, shorthand source specs resolve on your enterpr
Use full https://github.com/... source URLs for other public github.com workflows.
Note: To create a new workflow from scratch, use the 'new' command instead.
Note: For guided interactive setup, use the 'add-wizard' command instead.`,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/daily-repo-status # Add workflow directly
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/repo-assist # Add package from repository root aw.yml
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/packages/repo-assist # Add package from nested aw.yml
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/ci-doctor@v1.0.0 # Add with version
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/workflows/ci-doctor.md@main
` + string(constants.CLIExtensionPrefix) + ` add https://github.com/githubnext/agentics/blob/main/workflows/ci-doctor.md
` + string(constants.CLIExtensionPrefix) + ` add https://example.com/my-workflow.md # Add workflow from any HTTPS URL
` + string(constants.CLIExtensionPrefix) + ` add https://example.com/workflow.json # Import JSON workflow definition
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/ci-doctor --create-pull-request --force
` + string(constants.CLIExtensionPrefix) + ` add ./my-workflow.md # Add local workflow
` + string(constants.CLIExtensionPrefix) + ` add ./*.md # Add all local workflows
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/ci-doctor --dir .github/workflows/shared # Add to .github/workflows/shared/
`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/zoom-out] Trailing \n inconsistency: this Example field ends with a blank line before the closing backtick, but the majority of the 36 migrated commands do not. Seven files share this pattern (add_command.go, add_wizard_command.go, mcp_add.go, mcp_inspect.go, mcp_list.go, mcp_list_tools.go, trial_command.go). Cobra normalises trailing whitespace in help output so this is invisible at runtime, but the source inconsistency will make future diffs noisier.

💡 Suggested fix

Remove the blank line before the closing backtick in all seven files:

// Before
		Example: `  gh aw add ...
  gh aw add ./*.md
`,
// After
		Example: `  gh aw add ...
  gh aw add ./*.md`,

This makes the style consistent with the other 29 migrated commands.

Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("missing workflow specification\n\nUsage:\n %s <workflow>...\n\nExamples:\n %[1]s githubnext/agentics/daily-repo-status Add from repository\n %[1]s ./my-workflow.md Add local workflow\n\nRun '%[1]s --help' for more information", cmd.CommandPath())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/zoom-out] The Args validation error string still contains "Examples:" (plural, old style) as an embedded heading. This is unrelated to cobra.Command.Example: but is a user-facing string that now looks inconsistent post-migration. The same pattern exists in deploy_command.go (line 83).

💡 Optional cleanup

Update the error format string to use "Example:" (singular, matching cobra convention):

return fmt.Errorf(
    "missing workflow specification\n\nUsage:\n  %s <workflow>...\n\nExample:\n  ...",
    cmd.CommandPath(),
)

This is non-blocking but keeps user-visible text consistent.

Expand Down
21 changes: 10 additions & 11 deletions pkg/cli/add_wizard_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,6 @@ This command walks you through:

Use 'add' for non-interactive workflow addition.

Examples:
` + string(constants.CLIExtensionPrefix) + ` add-wizard githubnext/agentics # Guided setup for repository-root aw.yml package
` + string(constants.CLIExtensionPrefix) + ` add-wizard githubnext/agentics/packages/repo-assist # Guided setup for nested aw.yml package
` + string(constants.CLIExtensionPrefix) + ` add-wizard githubnext/agentics/daily-repo-status # Guided setup
` + string(constants.CLIExtensionPrefix) + ` add-wizard githubnext/agentics/ci-doctor@v1.0.0 # Guided setup with version
` + string(constants.CLIExtensionPrefix) + ` add-wizard ./my-workflow.md # Guided setup for local workflow
` + string(constants.CLIExtensionPrefix) + ` add-wizard https://example.com/my-workflow.md # Guided setup from any HTTPS URL
` + string(constants.CLIExtensionPrefix) + ` add-wizard https://example.com/workflow.json # Import JSON workflow definition with guided setup
` + string(constants.CLIExtensionPrefix) + ` add-wizard githubnext/agentics/ci-doctor --engine copilot # Pre-select engine
` + string(constants.CLIExtensionPrefix) + ` add-wizard githubnext/agentics/ci-doctor --skip-secret # Skip secret prompt

Workflow specifications:
- Two parts: "owner/repo[@version]" (loads repository-root aw.yml package)
- Three+ parts without .md: "owner/repo/folder[@version]" (loads nested aw.yml package when present)
Expand All @@ -55,6 +44,16 @@ Note: In GitHub Enterprise repos, shorthand specs resolve on your enterprise hos
For github/*, githubnext/*, and microsoft/*, shorthand resolves on github.com.
Use full https://github.com/... URLs when sourcing other public github.com workflows.
Note: To create a new workflow from scratch, use the 'new' command instead.`,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` add-wizard githubnext/agentics # Guided setup for repository-root aw.yml package
` + string(constants.CLIExtensionPrefix) + ` add-wizard githubnext/agentics/packages/repo-assist # Guided setup for nested aw.yml package
` + string(constants.CLIExtensionPrefix) + ` add-wizard githubnext/agentics/daily-repo-status # Guided setup
` + string(constants.CLIExtensionPrefix) + ` add-wizard githubnext/agentics/ci-doctor@v1.0.0 # Guided setup with version
` + string(constants.CLIExtensionPrefix) + ` add-wizard ./my-workflow.md # Guided setup for local workflow
` + string(constants.CLIExtensionPrefix) + ` add-wizard https://example.com/my-workflow.md # Guided setup from any HTTPS URL
` + string(constants.CLIExtensionPrefix) + ` add-wizard https://example.com/workflow.json # Import JSON workflow definition with guided setup
` + string(constants.CLIExtensionPrefix) + ` add-wizard githubnext/agentics/ci-doctor --engine copilot # Pre-select engine
` + string(constants.CLIExtensionPrefix) + ` add-wizard githubnext/agentics/ci-doctor --skip-secret # Skip secret prompt
`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("missing workflow specification\n\nRun 'gh aw add-wizard --help' for usage information")
Expand Down
6 changes: 2 additions & 4 deletions pkg/cli/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,8 @@ Each argument accepts:
When a job URL is provided (single-run mode only):
- If a step number is included (#step:7:1), extracts that specific step's output
- If no step number, finds and extracts the first failing step's output
- Saves job logs to the output directory

Examples:
` + string(constants.CLIExtensionPrefix) + ` audit 1234567890 # Audit run with ID 1234567890
- Saves job logs to the output directory`,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` audit 1234567890 # Audit run with ID 1234567890
` + string(constants.CLIExtensionPrefix) + ` audit https://github.com/owner/repo/actions/runs/1234567890 # Audit from run URL
` + string(constants.CLIExtensionPrefix) + ` audit https://github.com/owner/repo/actions/runs/1234567890/job/9876543210 # Audit job and extract first failing step
` + string(constants.CLIExtensionPrefix) + ` audit https://github.com/owner/repo/actions/runs/1234567890/job/9876543210#step:7:1 # Extract step 7 output
Expand Down
6 changes: 2 additions & 4 deletions pkg/cli/audit_diff_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,8 @@ analyzes their data, and produces a diff showing:
- Anomaly flags (new denied domains, previously-denied now allowed)
- MCP tool invocation changes (new/removed tools, call count and error count diffs)
- Run metrics comparison (token usage, duration, turns) when cached data is available
- Detailed token usage breakdown (input/output/cache + AI Credits) from firewall proxy

Examples:
` + string(constants.CLIExtensionPrefix) + ` audit diff 12345 12346 # Compare two runs
- Detailed token usage breakdown (input/output/cache + AI Credits) from firewall proxy`,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` audit diff 12345 12346 # Compare two runs
` + string(constants.CLIExtensionPrefix) + ` audit diff 12345 12346 12347 12348 # Compare base against 3 runs
` + string(constants.CLIExtensionPrefix) + ` audit diff 12345 12346 --format markdown # Markdown output for PR comments
` + string(constants.CLIExtensionPrefix) + ` audit diff 12345 12346 --json # JSON for CI integration
Expand Down
6 changes: 2 additions & 4 deletions pkg/cli/checks_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,8 @@ JSON output includes two state fields:
branch-protection or account-gate statuses fail

Use required_state as the authoritative CI verdict in repos that have optional
deployment integrations posting commit statuses alongside required CI checks.

Examples:
` + string(constants.CLIExtensionPrefix) + ` checks 42 # Classify checks for PR #42
deployment integrations posting commit statuses alongside required CI checks.`,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` checks 42 # Classify checks for PR #42
` + string(constants.CLIExtensionPrefix) + ` checks 42 --repo owner/repo # Specify repository
` + string(constants.CLIExtensionPrefix) + ` checks 42 --json # Output in JSON format`,
Args: cobra.ExactArgs(1),
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/cli_consistency_help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestAuditCommandDescriptionsAreConsistent(t *testing.T) {
func TestTrialCommandUsesStandardExamplesHeading(t *testing.T) {
cmd := NewTrialCommand(func(string) error { return nil })

assert.Contains(t, cmd.Long, "Examples:", "trial long help should use the standard examples heading")
assert.NotEmpty(t, cmd.Example, "trial command should use cobra's Example field for examples")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] assert.NotEmpty checks only the trial command; a new command added without an Example: field would silently regress. Since this PR just established the contract across 36 commands, now is a natural time to add a table-driven guard.

💡 Suggested extension
func TestAllCommandsHaveExampleField(t *testing.T) {
    root := NewRootCommand()
    for _, cmd := range root.Commands() {
        if cmd.Runnable() {
            assert.NotEmpty(t, cmd.Example, "%s should define cobra Example: field", cmd.Name())
        }
        for _, sub := range cmd.Commands() {
            if sub.Runnable() {
                assert.NotEmpty(t, sub.Example, "%s %s should define cobra Example: field", cmd.Name(), sub.Name())
            }
        }
    }
}

This acts as a lint guard preventing future regressions without any per-command boilerplate.

assert.NotContains(t, cmd.Long, "Single workflow:", "trial long help should avoid custom example section headings")
assert.NotContains(t, cmd.Long, "Multiple workflows (for comparison):", "trial long help should avoid custom example section headings")
assert.NotContains(t, cmd.Long, "Workflows from different repositories:", "trial long help should avoid custom example section headings")
Expand Down
18 changes: 6 additions & 12 deletions pkg/cli/completion_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@ Tab completion provides:
- Directory path completion for --dir flag
- Helpful descriptions for workflows when available

Supported shells: bash, zsh, fish, PowerShell

Examples:
# Install completions automatically (detects your shell)
Supported shells: bash, zsh, fish, PowerShell`,
Example: ` # Install completions automatically (detects your shell)
gh aw completion install

# Generate completion script for bash
Expand Down Expand Up @@ -81,10 +79,8 @@ Supported shells:
- Bash: Installs to ~/.bash_completion.d/ or /etc/bash_completion.d/
- Zsh: Installs to ~/.zsh/completions/
- Fish: Installs to ~/.config/fish/completions/
- PowerShell: Provides instructions to add to PowerShell profile

Examples:
gh aw completion install # Auto-detect and install
- PowerShell: Provides instructions to add to PowerShell profile`,
Example: ` gh aw completion install # Auto-detect and install
gh aw completion install --verbose # Show detailed installation steps`,
RunE: func(cmd *cobra.Command, args []string) error {
verbose, _ := cmd.Flags().GetBool("verbose")
Expand All @@ -107,10 +103,8 @@ Supported shells:
- Bash: Removes from ~/.bash_completion.d/ or /etc/bash_completion.d/
- Zsh: Removes from ~/.zsh/completions/
- Fish: Removes from ~/.config/fish/completions/
- PowerShell: Provides instructions to remove from PowerShell profile

Examples:
gh aw completion uninstall # Auto-detect and uninstall
- PowerShell: Provides instructions to remove from PowerShell profile`,
Example: ` gh aw completion uninstall # Auto-detect and uninstall
gh aw completion uninstall --verbose # Show detailed uninstallation steps`,
RunE: func(cmd *cobra.Command, args []string) error {
verbose, _ := cmd.Flags().GetBool("verbose")
Expand Down
10 changes: 5 additions & 5 deletions pkg/cli/completion_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,11 @@ func TestCompletionCommand_Examples(t *testing.T) {
cmd := NewCompletionCommand()

// Verify examples are present for all shells
assert.Contains(t, cmd.Long, "gh aw completion install")
assert.Contains(t, cmd.Long, "gh aw completion bash")
assert.Contains(t, cmd.Long, "gh aw completion zsh")
assert.Contains(t, cmd.Long, "gh aw completion fish")
assert.Contains(t, cmd.Long, "gh aw completion powershell")
assert.Contains(t, cmd.Example, "gh aw completion install")
assert.Contains(t, cmd.Example, "gh aw completion bash")
assert.Contains(t, cmd.Example, "gh aw completion zsh")
assert.Contains(t, cmd.Example, "gh aw completion fish")
assert.Contains(t, cmd.Example, "gh aw completion powershell")
}

func TestCompletionCommand_ValidArgs(t *testing.T) {
Expand Down
6 changes: 2 additions & 4 deletions pkg/cli/deploy_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ func NewDeployCommand(validateEngine func(string) error) *cobra.Command {
Short: "Deploy agentic workflows to a target repository using a pull request",
Long: `Deploy one or more workflows to a target repository by combining clone, update, add, compile, and pull request creation.

The command clones the target repository, updates existing workflows from source, adds the specified workflows, recompiles lock files with purge enabled, and opens a pull request.

Examples:
` + string(constants.CLIExtensionPrefix) + ` deploy githubnext/agentics/ci-doctor --repo owner/repo
The command clones the target repository, updates existing workflows from source, adds the specified workflows, recompiles lock files with purge enabled, and opens a pull request.`,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` deploy githubnext/agentics/ci-doctor --repo owner/repo
` + string(constants.CLIExtensionPrefix) + ` deploy githubnext/agentics/repo-assist githubnext/agentics/ci-doctor --repo owner/repo --force
` + string(constants.CLIExtensionPrefix) + ` deploy ./my-workflow.md --repo owner/repo`,
Args: validateDeployArgs,
Expand Down
6 changes: 2 additions & 4 deletions pkg/cli/domains_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,8 @@ domains for that workflow, including domains expanded from ecosystem identifiers

The workflow argument can be:
- A workflow ID (basename without .md extension, e.g., "weekly-research")
- A file path (e.g., "weekly-research.md" or ".github/workflows/weekly-research.md")

Examples:
` + string(constants.CLIExtensionPrefix) + ` domains # List all workflows with domain counts
- A file path (e.g., "weekly-research.md" or ".github/workflows/weekly-research.md")`,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` domains # List all workflows with domain counts
` + string(constants.CLIExtensionPrefix) + ` domains weekly-research # List domains for weekly-research workflow
` + string(constants.CLIExtensionPrefix) + ` domains --json # Output summary in JSON format
` + string(constants.CLIExtensionPrefix) + ` domains weekly-research --json # Output workflow domains in JSON format`,
Expand Down
Loading
Loading