Skip to content
Closed
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
35 changes: 35 additions & 0 deletions cmd/gh-aw/help_usage_examples_order_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//go:build !integration

package main

import (
"strings"
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)

func TestCommandsUseExampleFieldForHelpOrdering(t *testing.T) {
testCases := []struct {
name string
cmd *cobra.Command
}{
{name: "new", cmd: newCmd},
{name: "remove", cmd: removeCmd},
{name: "enable", cmd: enableCmd},
{name: "disable", cmd: disableCmd},
{name: "compile", cmd: compileCmd},
{name: "run", cmd: runCmd},
{name: "version", cmd: versionCmd},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
assert.NotEmpty(t, tt.cmd.Example, "command should define examples via Example field")
assert.NotContains(t, tt.cmd.Long, "\nExamples:\n", "command long help should not embed an Examples section")
assert.Contains(t, tt.cmd.Example, "gh aw ", "command examples should contain gh aw invocations")
assert.False(t, strings.HasPrefix(tt.cmd.Example, "Examples:"), "command Example field should contain example lines only")
})
}
}
40 changes: 14 additions & 26 deletions cmd/gh-aw/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,8 @@ When called with a workflow name, creates a template file with comprehensive exa
- Tools configuration (github, claude, MCPs)
- All frontmatter options with explanations

` + cli.WorkflowIDExplanation + `

Examples:
` + string(constants.CLIExtensionPrefix) + ` new # Interactive mode
` + cli.WorkflowIDExplanation,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` new # Interactive mode
` + string(constants.CLIExtensionPrefix) + ` new my-workflow # Create template file
` + string(constants.CLIExtensionPrefix) + ` new my-workflow.md # Same as above (.md extension stripped)
` + string(constants.CLIExtensionPrefix) + ` new my-workflow --force # Overwrite if exists
Expand Down Expand Up @@ -173,10 +171,8 @@ The workflow-id is the basename of the Markdown file without the .md extension.
You can provide a substring to match multiple workflows, or a specific workflow-id.

By default, this command also removes orphaned include files that are no longer referenced
by any workflow. Use --keep-orphans to skip this cleanup.

Examples:
` + string(constants.CLIExtensionPrefix) + ` remove my-workflow # Remove specific workflow
by any workflow. Use --keep-orphans to skip this cleanup.`,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` remove my-workflow # Remove specific workflow
` + string(constants.CLIExtensionPrefix) + ` remove test- # Remove all workflows containing 'test-' in name
` + string(constants.CLIExtensionPrefix) + ` remove old- --keep-orphans # Remove workflows but keep orphaned includes
` + string(constants.CLIExtensionPrefix) + ` remove my-workflow --dir .github/workflows/shared # Remove from custom directory`,
Expand All @@ -196,10 +192,8 @@ var enableCmd = &cobra.Command{
Short: "Enable agentic workflows",
Long: `Enable one or more workflows by ID, or all workflows if no IDs are provided.

` + cli.WorkflowIDExplanation + `

Examples:
` + string(constants.CLIExtensionPrefix) + ` enable # Enable all workflows
` + cli.WorkflowIDExplanation,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` enable # Enable all workflows
` + string(constants.CLIExtensionPrefix) + ` enable ci-doctor # Enable specific workflow
` + string(constants.CLIExtensionPrefix) + ` enable ci-doctor.md # Enable specific workflow (alternative format)
` + string(constants.CLIExtensionPrefix) + ` enable ci-doctor daily # Enable multiple workflows
Expand All @@ -217,10 +211,8 @@ var disableCmd = &cobra.Command{

Any in-progress runs will be cancelled before disabling.

` + cli.WorkflowIDExplanation + `

Examples:
` + string(constants.CLIExtensionPrefix) + ` disable # Disable all workflows
` + cli.WorkflowIDExplanation,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` disable # Disable all workflows
` + string(constants.CLIExtensionPrefix) + ` disable ci-doctor # Disable specific workflow
` + string(constants.CLIExtensionPrefix) + ` disable ci-doctor.md # Disable specific workflow (alternative format)
` + string(constants.CLIExtensionPrefix) + ` disable ci-doctor daily # Disable multiple workflows
Expand Down Expand Up @@ -275,9 +267,8 @@ Three flags govern this. --gh-aw-ref is mutually exclusive with the other two;
Branch and tag names are resolved via the GitHub API.
Cannot be combined with --action-tag or --action-mode.
Use this when E2E-testing compiled workflows against a specific gh-aw revision.

Examples:
` + string(constants.CLIExtensionPrefix) + ` compile # Compile all Markdown files
`,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` compile # Compile all Markdown files
` + string(constants.CLIExtensionPrefix) + ` compile ci-doctor # Compile a specific workflow
` + string(constants.CLIExtensionPrefix) + ` compile ci-doctor daily-plan # Compile multiple workflows
` + string(constants.CLIExtensionPrefix) + ` compile workflow.md # Compile by file path
Expand Down Expand Up @@ -428,10 +419,8 @@ The workflows must have been compiled into GitHub Actions YAML files.

This command only works with workflows that have workflow_dispatch triggers.

` + cli.WorkflowIDExplanation + `

Examples:
gh aw run # Interactive mode
` + cli.WorkflowIDExplanation,
Example: ` gh aw run # Interactive mode
gh aw run daily-perf-improver
gh aw run daily-perf-improver.md # Alternative format
gh aw run daily-perf-improver --ref main # Run on specific branch
Expand Down Expand Up @@ -502,9 +491,8 @@ var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the current version",
Long: `Show the installed version of the gh aw extension.

Examples:
` + string(constants.CLIExtensionPrefix) + ` version # Print the current version`,
`,
Example: ` ` + string(constants.CLIExtensionPrefix) + ` version # Print the current version`,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Fprintf(os.Stderr, "%s version %s\n", string(constants.CLIExtensionPrefix), version)
return nil
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/setup/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ Add workflows from The Agentics collection or other repositories to `.github/wor
```bash wrap
gh aw add githubnext/agentics/ci-doctor # Add single workflow
gh aw add githubnext/agentics/ci-doctor@v1.0.0 # Add specific version
gh aw add githubnext/agentics/ci-doctor --dir shared # Organize in subdirectory
gh aw add githubnext/agentics/ci-doctor --dir .github/workflows/shared # Organize in subdirectory
gh aw add githubnext/agentics/ci-doctor --create-pull-request # Create PR instead of commit
gh aw add https://example.com/workflows/my-workflow.md # Arbitrary HTTPS URL (markdown)
gh aw add https://example.com/workflows/my-workflow.json # Arbitrary HTTPS URL (JSON workflow definition)
Expand Down
5 changes: 4 additions & 1 deletion pkg/cli/lint_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ By default, shellcheck and pyflakes integrations are disabled for generated run
},
}

cmd.Flags().StringP("dir", "d", constants.GetWorkflowDir(), "Directory to scan for *.lock.yml files when no arguments are provided")
cmd.Flags().StringP("dir", "d", "", fmt.Sprintf("Workflow directory (default: %s)", constants.GetWorkflowDir()))
cmd.Flags().Bool("shellcheck", false, "Enable shellcheck integration in actionlint")
cmd.Flags().Bool("pyflakes", false, "Enable pyflakes integration in actionlint")

Expand All @@ -84,6 +84,9 @@ By default, shellcheck and pyflakes integrations are disabled for generated run
func resolveLockFilesForLint(inputs []string, workflowDir string) ([]string, error) {
candidates := inputs
if len(candidates) == 0 {
if workflowDir == "" {
workflowDir = constants.GetWorkflowDir()
}
candidates = []string{workflowDir}
}

Expand Down
19 changes: 18 additions & 1 deletion pkg/cli/lint_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"path/filepath"
"testing"

"github.com/github/gh-aw/pkg/constants"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand All @@ -17,7 +18,10 @@ func TestNewLintCommand(t *testing.T) {
require.NotNil(t, cmd, "NewLintCommand should return a non-nil command")
assert.Equal(t, "lint", cmd.Name(), "Command name should be 'lint'")
require.NotNil(t, cmd.Flags().Lookup("dir"), "lint command should have a --dir flag")
assert.Equal(t, "d", cmd.Flags().Lookup("dir").Shorthand, "--dir should have -d shorthand")
dirFlag := cmd.Flags().Lookup("dir")
assert.Equal(t, "d", dirFlag.Shorthand, "--dir should have -d shorthand")
assert.Empty(t, dirFlag.DefValue, "--dir default should be empty so help text controls default formatting")
assert.Equal(t, "Workflow directory (default: "+constants.GetWorkflowDir()+")", dirFlag.Usage, "--dir help text should match standard workflow-dir phrasing")
require.NotNil(t, cmd.Flags().Lookup("shellcheck"), "lint command should have a --shellcheck flag")
require.NotNil(t, cmd.Flags().Lookup("pyflakes"), "lint command should have a --pyflakes flag")
assert.Contains(t, defaultGhAwActionlintIgnorePatterns, `unknown permission scope "copilot-requests"`,
Expand Down Expand Up @@ -50,6 +54,19 @@ func TestResolveLockFilesForLint(t *testing.T) {
assert.Equal(t, []string{lockA, lockB}, files, "should return sorted .lock.yml files only")
})

t.Run("uses default workflow dir when dir is empty", func(t *testing.T) {
cwd := t.TempDir()
t.Chdir(cwd)
defaultWorkflowDir := filepath.Join(cwd, ".github", "workflows")
require.NoError(t, os.MkdirAll(defaultWorkflowDir, 0o755), "should create default workflow directory")
defaultLock := filepath.Join(defaultWorkflowDir, "default.lock.yml")
require.NoError(t, os.WriteFile(defaultLock, []byte("name: default"), 0o644), "should create lock file in default workflow directory")

files, err := resolveLockFilesForLint(nil, "")
require.NoError(t, err, "should resolve lock files from default workflow dir when --dir is omitted")
assert.Equal(t, []string{defaultLock}, files, "should lint lock files from .github/workflows by default")
})

t.Run("accepts explicit lock file path", func(t *testing.T) {
files, err := resolveLockFilesForLint([]string{lockB}, tempDir)
require.NoError(t, err, "should accept explicit lock file")
Expand Down