From d9bbfceab56eee61889ae314aec7140c8ae4dfdb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:07:53 +0000 Subject: [PATCH 1/3] Initial plan From e36cc399b0aa55cc159625bf3fca16ceaf10fdf0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:17:13 +0000 Subject: [PATCH 2/3] feat: add gh aw verify command as shorthand for compile --validate --no-emit with all linters Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- cmd/gh-aw/main.go | 3 + .../docs/reference/compilation-process.md | 2 + pkg/cli/verify_command.go | 63 +++++++++++++++++++ pkg/cli/verify_command_test.go | 25 ++++++++ 4 files changed, 93 insertions(+) create mode 100644 pkg/cli/verify_command.go create mode 100644 pkg/cli/verify_command_test.go diff --git a/cmd/gh-aw/main.go b/cmd/gh-aw/main.go index 04886f2f44f..085e74dd8e8 100644 --- a/cmd/gh-aw/main.go +++ b/cmd/gh-aw/main.go @@ -615,6 +615,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all hashCmd := cli.NewHashCommand() projectCmd := cli.NewProjectCommand() checksCmd := cli.NewChecksCommand() + verifyCmd := cli.NewVerifyCommand() // Assign commands to groups // Setup Commands @@ -628,6 +629,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all // Development Commands compileCmd.GroupID = "development" + verifyCmd.GroupID = "development" mcpCmd.GroupID = "development" statusCmd.GroupID = "development" listCmd.GroupID = "development" @@ -678,6 +680,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(secretsCmd) rootCmd.AddCommand(fixCmd) + rootCmd.AddCommand(verifyCmd) rootCmd.AddCommand(completionCmd) rootCmd.AddCommand(hashCmd) rootCmd.AddCommand(projectCmd) diff --git a/docs/src/content/docs/reference/compilation-process.md b/docs/src/content/docs/reference/compilation-process.md index 8e0ed41c399..35bc37a9f5e 100644 --- a/docs/src/content/docs/reference/compilation-process.md +++ b/docs/src/content/docs/reference/compilation-process.md @@ -323,6 +323,8 @@ Pre-activation runs checks sequentially. Any failure sets `activated=false`, pre | `gh aw compile --actionlint --zizmor --poutine` | Run security scanners | | `gh aw compile --purge` | Remove orphaned `.lock.yml` files | | `gh aw compile --output /path/to/output` | Custom output directory | +| `gh aw verify` | Verify all workflows (compile + all linters, no file output) | +| `gh aw verify my-workflow` | Verify a specific workflow | > [!TIP] > Compilation is only required when changing **frontmatter configuration**. The **markdown body** (AI instructions) is loaded at runtime and can be edited without recompilation. See [Editing Workflows](/gh-aw/guides/editing-workflows/) for details. diff --git a/pkg/cli/verify_command.go b/pkg/cli/verify_command.go new file mode 100644 index 00000000000..304ec47592f --- /dev/null +++ b/pkg/cli/verify_command.go @@ -0,0 +1,63 @@ +package cli + +import ( + "context" + + "github.com/github/gh-aw/pkg/constants" + "github.com/github/gh-aw/pkg/logger" + "github.com/spf13/cobra" +) + +var verifyLog = logger.New("cli:verify_command") + +// NewVerifyCommand creates the verify command +func NewVerifyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "verify [workflow]...", + Short: "Verify agentic workflows without generating lock files", + Long: `Verify one or more agentic workflows by compiling and running all linters without +generating lock files. This is equivalent to: + + gh aw compile --validate --no-emit --zizmor --actionlint --poutine + +If no workflows are specified, all Markdown files in .github/workflows will be verified. + +` + WorkflowIDExplanation + ` + +Examples: + ` + string(constants.CLIExtensionPrefix) + ` verify # Verify all workflows + ` + string(constants.CLIExtensionPrefix) + ` verify ci-doctor # Verify a specific workflow + ` + string(constants.CLIExtensionPrefix) + ` verify ci-doctor daily # Verify multiple workflows + ` + string(constants.CLIExtensionPrefix) + ` verify workflow.md # Verify by file path + ` + string(constants.CLIExtensionPrefix) + ` verify --dir custom/workflows # Verify from custom directory`, + RunE: func(cmd *cobra.Command, args []string) error { + dir, _ := cmd.Flags().GetString("dir") + verbose, _ := cmd.Flags().GetBool("verbose") + + verifyLog.Printf("Running verify command: workflows=%v, dir=%s", args, dir) + + config := CompileConfig{ + MarkdownFiles: args, + Verbose: verbose, + Validate: true, + NoEmit: true, + Zizmor: true, + Actionlint: true, + Poutine: true, + WorkflowDir: dir, + } + if _, err := CompileWorkflows(context.Background(), config); err != nil { + return err + } + return nil + }, + } + + cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: .github/workflows)") + + // Register completions + cmd.ValidArgsFunction = CompleteWorkflowNames + RegisterDirFlagCompletion(cmd, "dir") + + return cmd +} diff --git a/pkg/cli/verify_command_test.go b/pkg/cli/verify_command_test.go new file mode 100644 index 00000000000..6c3079ecb46 --- /dev/null +++ b/pkg/cli/verify_command_test.go @@ -0,0 +1,25 @@ +//go:build !integration + +package cli + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestNewVerifyCommand tests that the verify command is created correctly +func TestNewVerifyCommand(t *testing.T) { + cmd := NewVerifyCommand() + + require.NotNil(t, cmd, "NewVerifyCommand should return a non-nil command") + assert.Equal(t, "verify", cmd.Name(), "Command name should be 'verify'") + assert.NotEmpty(t, cmd.Short, "Command should have a short description") + assert.NotEmpty(t, cmd.Long, "Command should have a long description") + + // Verify the --dir flag exists + dirFlag := cmd.Flags().Lookup("dir") + require.NotNil(t, dirFlag, "verify command should have a --dir flag") + assert.Equal(t, "d", dirFlag.Shorthand, "--dir flag should have -d shorthand") +} From e8bbe5b873ee215f3ebca9b448556851d325e20b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:46:10 +0000 Subject: [PATCH 3/3] feat: rename verify to validate command, add all compile flags Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- cmd/gh-aw/main.go | 6 +- .../docs/reference/compilation-process.md | 6 +- pkg/cli/validate_command.go | 91 +++++++++++++++++++ pkg/cli/validate_command_test.go | 31 +++++++ pkg/cli/verify_command.go | 63 +------------ pkg/cli/verify_command_test.go | 23 +---- 6 files changed, 131 insertions(+), 89 deletions(-) create mode 100644 pkg/cli/validate_command.go create mode 100644 pkg/cli/validate_command_test.go diff --git a/cmd/gh-aw/main.go b/cmd/gh-aw/main.go index 085e74dd8e8..417550b78e2 100644 --- a/cmd/gh-aw/main.go +++ b/cmd/gh-aw/main.go @@ -615,7 +615,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all hashCmd := cli.NewHashCommand() projectCmd := cli.NewProjectCommand() checksCmd := cli.NewChecksCommand() - verifyCmd := cli.NewVerifyCommand() + validateCmd := cli.NewValidateCommand(validateEngine) // Assign commands to groups // Setup Commands @@ -629,7 +629,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all // Development Commands compileCmd.GroupID = "development" - verifyCmd.GroupID = "development" + validateCmd.GroupID = "development" mcpCmd.GroupID = "development" statusCmd.GroupID = "development" listCmd.GroupID = "development" @@ -680,7 +680,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(secretsCmd) rootCmd.AddCommand(fixCmd) - rootCmd.AddCommand(verifyCmd) + rootCmd.AddCommand(validateCmd) rootCmd.AddCommand(completionCmd) rootCmd.AddCommand(hashCmd) rootCmd.AddCommand(projectCmd) diff --git a/docs/src/content/docs/reference/compilation-process.md b/docs/src/content/docs/reference/compilation-process.md index 35bc37a9f5e..682cc24f13a 100644 --- a/docs/src/content/docs/reference/compilation-process.md +++ b/docs/src/content/docs/reference/compilation-process.md @@ -323,8 +323,10 @@ Pre-activation runs checks sequentially. Any failure sets `activated=false`, pre | `gh aw compile --actionlint --zizmor --poutine` | Run security scanners | | `gh aw compile --purge` | Remove orphaned `.lock.yml` files | | `gh aw compile --output /path/to/output` | Custom output directory | -| `gh aw verify` | Verify all workflows (compile + all linters, no file output) | -| `gh aw verify my-workflow` | Verify a specific workflow | +| `gh aw validate` | Validate all workflows (compile + all linters, no file output) | +| `gh aw validate my-workflow` | Validate a specific workflow | +| `gh aw validate --json` | Validate and output results in JSON format | +| `gh aw validate --strict` | Validate with strict mode enforced | > [!TIP] > Compilation is only required when changing **frontmatter configuration**. The **markdown body** (AI instructions) is loaded at runtime and can be edited without recompilation. See [Editing Workflows](/gh-aw/guides/editing-workflows/) for details. diff --git a/pkg/cli/validate_command.go b/pkg/cli/validate_command.go new file mode 100644 index 00000000000..b037c513812 --- /dev/null +++ b/pkg/cli/validate_command.go @@ -0,0 +1,91 @@ +package cli + +import ( + "context" + + "github.com/github/gh-aw/pkg/constants" + "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(validateEngine func(string) error) *cobra.Command { + cmd := &cobra.Command{ + Use: "validate [workflow]...", + Short: "Validate agentic workflows without generating lock files", + Long: `Validate one or more agentic workflows by compiling and running all linters without +generating lock files. This is equivalent to: + + gh aw compile --validate --no-emit --zizmor --actionlint --poutine + +If no workflows are specified, all Markdown files in .github/workflows will be validated. + +` + WorkflowIDExplanation + ` + +Examples: + ` + string(constants.CLIExtensionPrefix) + ` validate # Validate all workflows + ` + string(constants.CLIExtensionPrefix) + ` validate ci-doctor # Validate a specific workflow + ` + string(constants.CLIExtensionPrefix) + ` validate ci-doctor daily # Validate multiple workflows + ` + string(constants.CLIExtensionPrefix) + ` validate workflow.md # Validate by file path + ` + string(constants.CLIExtensionPrefix) + ` validate --dir custom/workflows # Validate from custom directory + ` + string(constants.CLIExtensionPrefix) + ` validate --json # Output results in JSON format + ` + string(constants.CLIExtensionPrefix) + ` validate --strict # Enforce strict mode validation + ` + string(constants.CLIExtensionPrefix) + ` validate --fail-fast # Stop at the first error`, + RunE: func(cmd *cobra.Command, args []string) error { + engineOverride, _ := cmd.Flags().GetString("engine") + dir, _ := cmd.Flags().GetString("dir") + strict, _ := cmd.Flags().GetBool("strict") + jsonOutput, _ := cmd.Flags().GetBool("json") + failFast, _ := cmd.Flags().GetBool("fail-fast") + stats, _ := cmd.Flags().GetBool("stats") + noCheckUpdate, _ := cmd.Flags().GetBool("no-check-update") + verbose, _ := cmd.Flags().GetBool("verbose") + + if err := validateEngine(engineOverride); err != nil { + return err + } + + // Check for updates (non-blocking, runs once per day) + CheckForUpdatesAsync(cmd.Context(), noCheckUpdate, verbose) + + validateLog.Printf("Running validate command: workflows=%v, dir=%s", args, dir) + + config := CompileConfig{ + MarkdownFiles: args, + Verbose: verbose, + EngineOverride: engineOverride, + Validate: true, + NoEmit: true, + Zizmor: true, + Actionlint: true, + Poutine: true, + WorkflowDir: dir, + Strict: strict, + JSONOutput: jsonOutput, + FailFast: failFast, + Stats: stats, + } + if _, err := CompileWorkflows(context.Background(), config); err != nil { + return err + } + return nil + }, + } + + cmd.Flags().StringP("engine", "e", "", "Override AI engine (claude, codex, copilot, custom)") + cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: .github/workflows)") + cmd.Flags().Bool("strict", false, "Enforce strict mode validation for all workflows") + cmd.Flags().BoolP("json", "j", false, "Output results in JSON format") + cmd.Flags().Bool("fail-fast", false, "Stop at the first validation error instead of collecting all errors") + cmd.Flags().Bool("stats", false, "Display statistics table sorted by file size") + cmd.Flags().Bool("no-check-update", false, "Skip checking for gh-aw updates") + + // Register completions + cmd.ValidArgsFunction = CompleteWorkflowNames + RegisterEngineFlagCompletion(cmd) + RegisterDirFlagCompletion(cmd, "dir") + + return cmd +} diff --git a/pkg/cli/validate_command_test.go b/pkg/cli/validate_command_test.go new file mode 100644 index 00000000000..7ecd8a288b0 --- /dev/null +++ b/pkg/cli/validate_command_test.go @@ -0,0 +1,31 @@ +//go:build !integration + +package cli + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestNewValidateCommand tests that the validate command is created correctly +func TestNewValidateCommand(t *testing.T) { + cmd := NewValidateCommand(func(string) error { return nil }) + + require.NotNil(t, cmd, "NewValidateCommand should return a non-nil command") + assert.Equal(t, "validate", cmd.Name(), "Command name should be 'validate'") + assert.NotEmpty(t, cmd.Short, "Command should have a short description") + assert.NotEmpty(t, cmd.Long, "Command should have a long description") + + // Verify key flags exist + require.NotNil(t, cmd.Flags().Lookup("dir"), "validate command should have a --dir flag") + assert.Equal(t, "d", cmd.Flags().Lookup("dir").Shorthand, "--dir flag should have -d shorthand") + require.NotNil(t, cmd.Flags().Lookup("json"), "validate command should have a --json flag") + assert.Equal(t, "j", cmd.Flags().Lookup("json").Shorthand, "--json flag should have -j shorthand") + require.NotNil(t, cmd.Flags().Lookup("engine"), "validate command should have a --engine flag") + require.NotNil(t, cmd.Flags().Lookup("strict"), "validate command should have a --strict flag") + require.NotNil(t, cmd.Flags().Lookup("fail-fast"), "validate command should have a --fail-fast flag") + require.NotNil(t, cmd.Flags().Lookup("stats"), "validate command should have a --stats flag") + require.NotNil(t, cmd.Flags().Lookup("no-check-update"), "validate command should have a --no-check-update flag") +} diff --git a/pkg/cli/verify_command.go b/pkg/cli/verify_command.go index 304ec47592f..d10eb2789e6 100644 --- a/pkg/cli/verify_command.go +++ b/pkg/cli/verify_command.go @@ -1,63 +1,2 @@ +// Package cli - verify_command.go is superseded by validate_command.go. package cli - -import ( - "context" - - "github.com/github/gh-aw/pkg/constants" - "github.com/github/gh-aw/pkg/logger" - "github.com/spf13/cobra" -) - -var verifyLog = logger.New("cli:verify_command") - -// NewVerifyCommand creates the verify command -func NewVerifyCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "verify [workflow]...", - Short: "Verify agentic workflows without generating lock files", - Long: `Verify one or more agentic workflows by compiling and running all linters without -generating lock files. This is equivalent to: - - gh aw compile --validate --no-emit --zizmor --actionlint --poutine - -If no workflows are specified, all Markdown files in .github/workflows will be verified. - -` + WorkflowIDExplanation + ` - -Examples: - ` + string(constants.CLIExtensionPrefix) + ` verify # Verify all workflows - ` + string(constants.CLIExtensionPrefix) + ` verify ci-doctor # Verify a specific workflow - ` + string(constants.CLIExtensionPrefix) + ` verify ci-doctor daily # Verify multiple workflows - ` + string(constants.CLIExtensionPrefix) + ` verify workflow.md # Verify by file path - ` + string(constants.CLIExtensionPrefix) + ` verify --dir custom/workflows # Verify from custom directory`, - RunE: func(cmd *cobra.Command, args []string) error { - dir, _ := cmd.Flags().GetString("dir") - verbose, _ := cmd.Flags().GetBool("verbose") - - verifyLog.Printf("Running verify command: workflows=%v, dir=%s", args, dir) - - config := CompileConfig{ - MarkdownFiles: args, - Verbose: verbose, - Validate: true, - NoEmit: true, - Zizmor: true, - Actionlint: true, - Poutine: true, - WorkflowDir: dir, - } - if _, err := CompileWorkflows(context.Background(), config); err != nil { - return err - } - return nil - }, - } - - cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: .github/workflows)") - - // Register completions - cmd.ValidArgsFunction = CompleteWorkflowNames - RegisterDirFlagCompletion(cmd, "dir") - - return cmd -} diff --git a/pkg/cli/verify_command_test.go b/pkg/cli/verify_command_test.go index 6c3079ecb46..80fb510112a 100644 --- a/pkg/cli/verify_command_test.go +++ b/pkg/cli/verify_command_test.go @@ -1,25 +1,4 @@ //go:build !integration +// Package cli - verify_command_test.go is superseded by validate_command_test.go. package cli - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestNewVerifyCommand tests that the verify command is created correctly -func TestNewVerifyCommand(t *testing.T) { - cmd := NewVerifyCommand() - - require.NotNil(t, cmd, "NewVerifyCommand should return a non-nil command") - assert.Equal(t, "verify", cmd.Name(), "Command name should be 'verify'") - assert.NotEmpty(t, cmd.Short, "Command should have a short description") - assert.NotEmpty(t, cmd.Long, "Command should have a long description") - - // Verify the --dir flag exists - dirFlag := cmd.Flags().Lookup("dir") - require.NotNil(t, dirFlag, "verify command should have a --dir flag") - assert.Equal(t, "d", dirFlag.Shorthand, "--dir flag should have -d shorthand") -}