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
8 changes: 2 additions & 6 deletions pkg/cli/add_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,7 @@ func runAddCommand(cmd *cobra.Command, args []string, validateEngine func(string
workflowDir, _ := cmd.Flags().GetString("dir")
noStopAfter, _ := cmd.Flags().GetBool("no-stop-after")
stopAfter, _ := cmd.Flags().GetString("stop-after")
disableSecurityScanner, _ := cmd.Flags().GetBool("no-security-scanner")
disableSecurityScannerLegacy, _ := cmd.Flags().GetBool("disable-security-scanner")
disableSecurityScanner = disableSecurityScanner || disableSecurityScannerLegacy
disableSecurityScanner := resolveDeprecatedBoolFlag(cmd, "no-security-scanner", "disable-security-scanner")

if nameFlag != "" && len(args) > 1 {
return errors.New("--name flag cannot be used when adding multiple workflows at once")
Expand Down Expand Up @@ -219,9 +217,7 @@ func registerAddCommandFlags(cmd *cobra.Command) {
cmd.Flags().String("stop-after", "", "Override stop-after value in the workflow (e.g., '+48h', '2025-12-31 23:59:59')")

// Add no-security-scanner flag to add command (--disable-security-scanner is kept as a deprecated alias)
cmd.Flags().Bool("no-security-scanner", false, "Skip security scanning of workflow markdown content")
cmd.Flags().Bool("disable-security-scanner", false, "Skip security scanning of workflow markdown content")
_ = cmd.Flags().MarkDeprecated("disable-security-scanner", "use --no-security-scanner instead")
addSecurityScannerFlag(cmd)

// Register completions for add command
RegisterEngineFlagCompletion(cmd)
Expand Down
9 changes: 0 additions & 9 deletions pkg/cli/add_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,6 @@ func TestNewAddCommand_MentionsEnterpriseSourceResolution(t *testing.T) {
assert.Contains(t, cmd.Long, "Use full https://github.com/... source URLs for other public github.com workflows.")
}

func TestNewAddCommand_DeprecatesDisableSecurityScannerFlag(t *testing.T) {
cmd := NewAddCommand(validateEngineStub)
require.NotNil(t, cmd)

flag := cmd.Flags().Lookup("disable-security-scanner")
require.NotNil(t, flag, "add command should keep --disable-security-scanner as a deprecated alias")
assert.Equal(t, "use --no-security-scanner instead", flag.Deprecated)
}

func TestAddWorkflows(t *testing.T) {
tests := []struct {
name string
Expand Down
8 changes: 2 additions & 6 deletions pkg/cli/add_wizard_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,7 @@ Note: To create a new workflow from scratch, use the 'new' command instead.`,
skipSecretLegacy, _ := cmd.Flags().GetBool("skip-secret")
skipSecret := noSecret || skipSecretLegacy
appendText, _ := cmd.Flags().GetString("append")
disableSecurityScanner, _ := cmd.Flags().GetBool("no-security-scanner")
disableSecurityScannerLegacy, _ := cmd.Flags().GetBool("disable-security-scanner")
disableSecurityScanner = disableSecurityScanner || disableSecurityScannerLegacy
disableSecurityScanner := resolveDeprecatedBoolFlag(cmd, "no-security-scanner", "disable-security-scanner")

addWizardLog.Printf("Starting add-wizard: workflows=%v, engine=%s, verbose=%v", workflows, engineOverride, verbose)

Expand Down Expand Up @@ -131,9 +129,7 @@ Note: To create a new workflow from scratch, use the 'new' command instead.`,

// Add no-security-scanner flag (--disable-security-scanner is kept as a deprecated alias
// for consistency with add and other install entry points)
cmd.Flags().Bool("no-security-scanner", false, "Skip security scanning of workflow markdown content")
cmd.Flags().Bool("disable-security-scanner", false, "Skip security scanning of workflow markdown content")
_ = cmd.Flags().MarkDeprecated("disable-security-scanner", "use --no-security-scanner instead")
addSecurityScannerFlag(cmd)

// Register completions
RegisterEngineFlagCompletion(cmd)
Expand Down
9 changes: 0 additions & 9 deletions pkg/cli/add_wizard_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,6 @@ func TestAddWizardCommand_FlagUsageMatchesAddCommand(t *testing.T) {
}
}

func TestAddWizardCommand_DeprecatesDisableSecurityScannerFlag(t *testing.T) {
cmd := NewAddWizardCommand(validateEngineStub)
require.NotNil(t, cmd)

flag := cmd.Flags().Lookup("disable-security-scanner")
require.NotNil(t, flag, "add-wizard command should keep --disable-security-scanner as a deprecated alias")
assert.Equal(t, "use --no-security-scanner instead", flag.Deprecated)
}

func TestAddWizardCommand_ExamplesMentionNewFlags(t *testing.T) {
cmd := NewAddWizardCommand(func(string) error { return nil })
require.NotNil(t, cmd)
Expand Down
8 changes: 2 additions & 6 deletions pkg/cli/deploy_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ func registerDeployFlags(cmd *cobra.Command) {
cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: $GH_AW_WORKFLOWS_DIR or .github/workflows)")
cmd.Flags().Bool("no-stop-after", false, "Remove any stop-after field from the workflow")
cmd.Flags().String("stop-after", "", "Override stop-after value in the workflow (e.g., '+48h', '2025-12-31 23:59:59')")
cmd.Flags().Bool("no-security-scanner", false, "Skip security scanning of workflow markdown content")
cmd.Flags().Bool("disable-security-scanner", false, "Skip security scanning of workflow markdown content")
_ = cmd.Flags().MarkDeprecated("disable-security-scanner", "use --no-security-scanner instead")
addSecurityScannerFlag(cmd)
cmd.Flags().String("cool-down", defaultDeployCooldown, coolDownFlagUsage)
cmd.Flags().String("org", "", "Deploy workflows across repositories in an organization")
cmd.Flags().StringSlice("repos", nil, "Limit --org mode to repositories matching one or more glob patterns")
Expand Down Expand Up @@ -149,9 +147,7 @@ func parseDeployCommandOptions(cmd *cobra.Command, workflows []string, validateE
workflowDir, _ := cmd.Flags().GetString("dir")
noStopAfter, _ := cmd.Flags().GetBool("no-stop-after")
stopAfter, _ := cmd.Flags().GetString("stop-after")
disableSecurityScanner, _ := cmd.Flags().GetBool("no-security-scanner")
disableSecurityScannerLegacy, _ := cmd.Flags().GetBool("disable-security-scanner")
disableSecurityScanner = disableSecurityScanner || disableSecurityScannerLegacy
disableSecurityScanner := resolveDeprecatedBoolFlag(cmd, "no-security-scanner", "disable-security-scanner")
coolDownStr, _ := cmd.Flags().GetString("cool-down")

if nameFlag != "" && len(workflows) > 1 {
Expand Down
9 changes: 0 additions & 9 deletions pkg/cli/deploy_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,6 @@ func TestNewDeployCommand_CoolDownFlagUsageMatchesUpdate(t *testing.T) {
assert.Equal(t, coolDownFlagUsage, coolDownFlag.Usage)
}

func TestNewDeployCommand_DeprecatesDisableSecurityScannerFlag(t *testing.T) {
cmd := NewDeployCommand(func(string) error { return nil })
require.NotNil(t, cmd)

flag := cmd.Flags().Lookup("disable-security-scanner")
require.NotNil(t, flag, "deploy command should keep --disable-security-scanner as a deprecated alias")
assert.Equal(t, "use --no-security-scanner instead", flag.Deprecated)
}

func TestNewDeployCommand_RequiresRepoFlag(t *testing.T) {
cmd := NewDeployCommand(func(string) error { return nil })
require.NotNil(t, cmd)
Expand Down
18 changes: 18 additions & 0 deletions pkg/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,21 @@ func addOutputFlag(cmd *cobra.Command, defaultValue string) {
func addJSONFlag(cmd *cobra.Command) {
cmd.Flags().BoolP("json", "j", false, "Output results in JSON format")
}

// addSecurityScannerFlag adds the --no-security-scanner flag and its deprecated
// --disable-security-scanner alias to a command.
func addSecurityScannerFlag(cmd *cobra.Command) {
cmd.Flags().Bool("no-security-scanner", false, "Skip security scanning of workflow markdown content")
cmd.Flags().Bool("disable-security-scanner", false, "Skip security scanning of workflow markdown content")
_ = cmd.Flags().MarkDeprecated("disable-security-scanner", "use --no-security-scanner instead")
}

// resolveDeprecatedBoolFlag returns true if either the newName flag or the
// deprecated oldName flag is set on cmd. It is intended for cases where a flag
// has been renamed: callers register both names and use this helper to collapse
// them into a single effective value.
func resolveDeprecatedBoolFlag(cmd *cobra.Command, newName, oldName string) bool {
newVal, _ := cmd.Flags().GetBool(newName)
oldVal, _ := cmd.Flags().GetBool(oldName)

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.

[/codebase-design] resolveDeprecatedBoolFlag silently discards errors from GetBool. If either flag name is misspelled at a call site, the function returns false without any indication of the mistake — the call site will behave incorrectly and no test will catch it at registration time.

💡 Suggestion

Panic (or use must-style) on a programming error at startup, consistent with how cobra itself handles unknown-flag access in tests:

func resolveDeprecatedBoolFlag(cmd *cobra.Command, newName, oldName string) bool {
	newVal, err := cmd.Flags().GetBool(newName)
	if err != nil {
		panic(fmt.Sprintf("resolveDeprecatedBoolFlag: flag %q not registered: %v", newName, err))
	}
	oldVal, err := cmd.Flags().GetBool(oldName)
	if err != nil {
		panic(fmt.Sprintf("resolveDeprecatedBoolFlag: flag %q not registered: %v", oldName, err))
	}
	return newVal || oldVal
}

A panic at startup is far easier to diagnose than a silent false in production. Alternatively, return (bool, error) if callers can handle it, but a panic is idiomatic for programmer errors in cobra-based CLIs.

@copilot please address this.

return newVal || oldVal
}
65 changes: 65 additions & 0 deletions pkg/cli/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,68 @@ func TestEngineFlagUsageText(t *testing.T) {
t.Errorf("Unexpected --engine filter usage text: %s", filterFlag.Usage)
}
}

func TestAddSecurityScannerFlag(t *testing.T) {
t.Parallel()

cmd := &cobra.Command{Use: "test"}
addSecurityScannerFlag(cmd)

primary := cmd.Flags().Lookup("no-security-scanner")
if primary == nil {
t.Fatal("addSecurityScannerFlag should register --no-security-scanner")
}
if primary.Usage != "Skip security scanning of workflow markdown content" {
t.Errorf("Unexpected --no-security-scanner usage: %s", primary.Usage)
}

deprecated := cmd.Flags().Lookup("disable-security-scanner")
if deprecated == nil {
t.Fatal("addSecurityScannerFlag should register --disable-security-scanner as a deprecated alias")
}
if deprecated.Deprecated != "use --no-security-scanner instead" {
t.Errorf("Expected deprecation message 'use --no-security-scanner instead', got %q", deprecated.Deprecated)
}
}

func TestResolveDeprecatedBoolFlag(t *testing.T) {
t.Parallel()

setup := func() *cobra.Command {
cmd := &cobra.Command{Use: "test"}
cmd.Flags().Bool("new-flag", false, "new flag")
cmd.Flags().Bool("old-flag", false, "old flag")
_ = cmd.Flags().MarkDeprecated("old-flag", "use --new-flag instead")
return cmd
}

t.Run("both false returns false", func(t *testing.T) {
t.Parallel()
cmd := setup()
if resolveDeprecatedBoolFlag(cmd, "new-flag", "old-flag") {
t.Error("expected false when both flags are unset")
}
})

t.Run("new flag true returns true", func(t *testing.T) {
t.Parallel()
cmd := setup()
if err := cmd.Flags().Set("new-flag", "true"); err != nil {
t.Fatalf("failed to set new-flag: %v", err)
}
if !resolveDeprecatedBoolFlag(cmd, "new-flag", "old-flag") {
t.Error("expected true when new flag is set")
}
})

t.Run("old flag true returns true", func(t *testing.T) {
t.Parallel()
cmd := setup()
if err := cmd.Flags().Set("old-flag", "true"); err != nil {
t.Fatalf("failed to set old-flag: %v", err)
}
if !resolveDeprecatedBoolFlag(cmd, "new-flag", "old-flag") {
t.Error("expected true when deprecated old flag is set")
}
})
}

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] TestResolveDeprecatedBoolFlag and TestAddSecurityScannerFlag cover the security-scanner pair, but there is no test for the --no-release-bump/--disable-release-bump pair that was also migrated to resolveDeprecatedBoolFlag in this PR.

TestNewUpdateCommand_DeprecatesDisableReleaseBumpFlag was deleted from update_command_test.go without a corresponding canonical test in flags_test.go. The deprecation message on --disable-release-bump is now untested.

💡 Suggestion

Add a TestAddReleaseBumpFlag (or extend TestAddSecurityScannerFlag into a table-driven test) covering the --no-release-bump / --disable-release-bump pair:

func TestAddReleaseBumpFlag(t *testing.T) {
	t.Parallel()
	cmd := &cobra.Command{Use: "test"}
	addReleaseBumpFlag(cmd)

	deprecated := cmd.Flags().Lookup("disable-release-bump")
	if deprecated == nil {
		t.Fatal("addReleaseBumpFlag should register --disable-release-bump")
	}
	if deprecated.Deprecated != "use --no-release-bump instead" {
		t.Errorf("unexpected deprecation message: %q", deprecated.Deprecated)
	}
}

@copilot please address this.

12 changes: 3 additions & 9 deletions pkg/cli/trial_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,7 @@ Trial results are saved both locally (in the trials/ directory) and in the host
cloneRepoSpec, _ := cmd.Flags().GetString("clone-repo")
hostRepoSpec, _ := cmd.Flags().GetString("host-repo")
deleteHostRepo, _ := cmd.Flags().GetBool("delete-host-repo-after")
legacyForceDelete, _ := cmd.Flags().GetBool("force-delete-host-repo-before")
deleteHostRepoBefore, _ := cmd.Flags().GetBool("delete-host-repo-before")
forceDeleteHostRepo := legacyForceDelete || deleteHostRepoBefore
forceDeleteHostRepo := resolveDeprecatedBoolFlag(cmd, "delete-host-repo-before", "force-delete-host-repo-before")
yes, _ := cmd.Flags().GetBool("yes")
dryRun, _ := cmd.Flags().GetBool("dry-run")
jsonOutput, _ := cmd.Flags().GetBool("json")
Expand All @@ -68,9 +66,7 @@ Trial results are saved both locally (in the trials/ directory) and in the host
engineOverride, _ := cmd.Flags().GetString("engine")
appendText, _ := cmd.Flags().GetString("append")
verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose")
disableSecurityScanner, _ := cmd.Flags().GetBool("no-security-scanner")
disableSecurityScannerLegacy, _ := cmd.Flags().GetBool("disable-security-scanner")
disableSecurityScanner = disableSecurityScanner || disableSecurityScannerLegacy
disableSecurityScanner := resolveDeprecatedBoolFlag(cmd, "no-security-scanner", "disable-security-scanner")

if err := validateEngine(engineOverride); err != nil {
trialLog.Printf("Engine validation failed: engine=%s, err=%v", engineOverride, err)
Expand Down Expand Up @@ -126,9 +122,7 @@ Trial results are saved both locally (in the trials/ directory) and in the host
addEngineFlag(cmd)
addJSONFlag(cmd)
cmd.Flags().String("append", "", "Append extra content to the end of the agentic workflow on installation")
cmd.Flags().Bool("no-security-scanner", false, "Skip security scanning of workflow markdown content")
cmd.Flags().Bool("disable-security-scanner", false, "Skip security scanning of workflow markdown content")
_ = cmd.Flags().MarkDeprecated("disable-security-scanner", "use --no-security-scanner instead")
addSecurityScannerFlag(cmd)
cmd.MarkFlagsMutuallyExclusive("logical-repo", "clone-repo")

return cmd
Expand Down
11 changes: 0 additions & 11 deletions pkg/cli/trial_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"testing"

"github.com/github/gh-aw/pkg/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewTrialCommandCloneRepoFlagDescription(t *testing.T) {
Expand Down Expand Up @@ -47,15 +45,6 @@ func TestNewTrialCommandNoArgsErrorIncludesExample(t *testing.T) {
}
}

func TestNewTrialCommand_DeprecatesDisableSecurityScannerFlag(t *testing.T) {
cmd := NewTrialCommand(func(string) error { return nil })
require.NotNil(t, cmd)

flag := cmd.Flags().Lookup("disable-security-scanner")
require.NotNil(t, flag, "trial command should keep --disable-security-scanner as a deprecated alias")
assert.Equal(t, "use --no-security-scanner instead", flag.Deprecated)
}

// Test the host repo slug processing logic with dot notation
func TestHostRepoSlugProcessing(t *testing.T) {
testCases := []struct {
Expand Down
12 changes: 3 additions & 9 deletions pkg/cli/update_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,10 @@ Note: In GitHub Enterprise repos, shorthand source specs resolve on your enterpr
noStopAfter, _ := cmd.Flags().GetBool("no-stop-after")
stopAfter, _ := cmd.Flags().GetString("stop-after")
noMergeFlag, _ := cmd.Flags().GetBool("no-merge")
disableReleaseBump, _ := cmd.Flags().GetBool("no-release-bump")
disableReleaseBumpLegacy, _ := cmd.Flags().GetBool("disable-release-bump")
disableReleaseBump = disableReleaseBump || disableReleaseBumpLegacy
disableReleaseBump := resolveDeprecatedBoolFlag(cmd, "no-release-bump", "disable-release-bump")
noCompile, _ := cmd.Flags().GetBool("no-compile")
noRedirect, _ := cmd.Flags().GetBool("no-redirect")
disableSecurityScanner, _ := cmd.Flags().GetBool("no-security-scanner")
disableSecurityScannerLegacy, _ := cmd.Flags().GetBool("disable-security-scanner")
disableSecurityScanner = disableSecurityScanner || disableSecurityScannerLegacy
disableSecurityScanner := resolveDeprecatedBoolFlag(cmd, "no-security-scanner", "disable-security-scanner")
approveFlag, _ := cmd.Flags().GetBool("approve")
createPRFlag, _ := cmd.Flags().GetBool("create-pull-request")
prFlagAlias, _ := cmd.Flags().GetBool("pr")
Expand Down Expand Up @@ -175,9 +171,7 @@ Note: In GitHub Enterprise repos, shorthand source specs resolve on your enterpr
cmd.Flags().Bool("no-release-bump", false, "Restrict automatic major version bumps to core actions/* only (non-core actions are left as-is)")
cmd.Flags().Bool("disable-release-bump", false, "Restrict automatic major version bumps to core actions/* only (non-core actions are left as-is)")
_ = cmd.Flags().MarkDeprecated("disable-release-bump", "use --no-release-bump instead")

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.

[/codebase-design] The --no-release-bump/--disable-release-bump pair is still registered inline here, inconsistent with the addSecurityScannerFlag pattern introduced in this PR.

💡 Suggestion

Extract an addReleaseBumpFlag(cmd) helper in flags.go alongside addSecurityScannerFlag and replace these three lines:

func addReleaseBumpFlag(cmd *cobra.Command) {
	cmd.Flags().Bool("no-release-bump", false, "Restrict automatic major version bumps...")
	cmd.Flags().Bool("disable-release-bump", false, "Restrict automatic major version bumps...")
	_ = cmd.Flags().MarkDeprecated("disable-release-bump", "use --no-release-bump instead")
}

Then call addReleaseBumpFlag(cmd) at the registration site. All deprecated-pair registrations are then in one place, eliminating this last inline exception.

@copilot please address this.

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.

The no-release-bump/disable-release-bump registration block (lines 171–173) was not extracted into a helper the way the security-scanner pair was. For full consistency — and so that the deprecated-alias contract is tested centrally — consider adding an addReleaseBumpFlag(cmd) helper in flags.go (mirroring addSecurityScannerFlag) and a matching TestAddReleaseBumpFlag test in flags_test.go. The deleted TestNewUpdateCommand_DeprecatesDisableReleaseBumpFlag had value; removing it without a central replacement leaves the deprecation message untested. @copilot please address this.

cmd.Flags().Bool("no-security-scanner", false, "Skip security scanning of workflow markdown content")
cmd.Flags().Bool("disable-security-scanner", false, "Skip security scanning of workflow markdown content")
_ = cmd.Flags().MarkDeprecated("disable-security-scanner", "use --no-security-scanner instead")
addSecurityScannerFlag(cmd)
cmd.Flags().Bool("approve", false, "Approve all safe update changes. When strict mode is active (the default), the compiler emits warnings for new restricted secrets or unapproved action additions/removals not present in the existing gh-aw-manifest. Use this flag to approve and skip safe update enforcement")
cmd.Flags().Bool("no-compile", false, "Skip recompiling workflows during update (do not modify lock files)")
cmd.Flags().Bool("no-redirect", false, "Refuse updates when redirect frontmatter is present")
Expand Down
23 changes: 0 additions & 23 deletions pkg/cli/update_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,29 +82,6 @@ This is the base content.`
}
}

func TestNewUpdateCommand_HasDisableSecurityScannerFlag(t *testing.T) {
cmd := NewUpdateCommand(func(string) error { return nil })
require.NotNil(t, cmd, "update command should be created")

flag := cmd.Flags().Lookup("no-security-scanner")
require.NotNil(t, flag, "update command should register --no-security-scanner")
assert.Equal(t, "Skip security scanning of workflow markdown content", flag.Usage, "flag help text should match add/trial wording")

// Deprecated alias should still be registered
deprecated := cmd.Flags().Lookup("disable-security-scanner")
require.NotNil(t, deprecated, "update command should keep --disable-security-scanner as a deprecated alias")
assert.Equal(t, "use --no-security-scanner instead", deprecated.Deprecated)
}

func TestNewUpdateCommand_DeprecatesDisableReleaseBumpFlag(t *testing.T) {
cmd := NewUpdateCommand(func(string) error { return nil })
require.NotNil(t, cmd)

flag := cmd.Flags().Lookup("disable-release-bump")
require.NotNil(t, flag, "update command should keep --disable-release-bump as a deprecated alias")
assert.Equal(t, "use --no-release-bump instead", flag.Deprecated)
}

func TestNewUpdateCommand_CoolDownFlagUsage(t *testing.T) {
cmd := NewUpdateCommand(func(string) error { return nil })
require.NotNil(t, cmd)
Expand Down
Loading