diff --git a/cmd/gh-aw/main.go b/cmd/gh-aw/main.go index 917bf67d46f..21366a07cd9 100644 --- a/cmd/gh-aw/main.go +++ b/cmd/gh-aw/main.go @@ -429,7 +429,7 @@ This command only works with workflows that have workflow_dispatch triggers. gh aw run daily-perf-improver --auto-merge-prs # Auto-merge any PRs created during execution gh aw run daily-perf-improver -F name=value -F env=prod # Pass workflow inputs gh aw run daily-perf-improver --push # Commit and push workflow files before running - gh aw run daily-perf-improver --dry-run # Validate without actually running + gh aw run daily-perf-improver --dry-run # Validate without triggering execution on GitHub Actions gh aw run daily-perf-improver --json # Output results in JSON format`, Args: cobra.ArbitraryArgs, RunE: func(cmd *cobra.Command, args []string) error { @@ -706,7 +706,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all cli.RegisterEngineFlagCompletion(initCmd) // Add flags to new command - newCmd.Flags().BoolP("force", "f", false, "Overwrite existing files without confirmation") + newCmd.Flags().BoolP("force", "f", false, "Overwrite existing workflow files without confirmation") newCmd.Flags().BoolP("interactive", "i", false, "Launch interactive workflow creation wizard") newCmd.Flags().StringP("engine", "e", "", "Override AI engine (copilot, claude, codex, gemini, crush)") cli.RegisterEngineFlagCompletion(newCmd) @@ -746,7 +746,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all compileCmd.Flags().Bool("no-check-update", false, "Skip checking for gh-aw updates") compileCmd.Flags().String("schedule-seed", "", "Override the repository slug (owner/repo) used as seed for fuzzy schedule scattering (e.g., \"github/gh-aw\"). Bypasses git remote detection entirely. Use this when your git remote is not named \"origin\" and you have multiple remotes configured") compileCmd.Flags().Bool("staged", false, "Force all safe-outputs into staged mode") - compileCmd.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") + compileCmd.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.") compileCmd.Flags().Bool("validate-images", false, "Require Docker to be available for container image validation. Without this flag, container image validation is silently skipped when Docker is not installed or the daemon is not running") compileCmd.Flags().String("prior-manifest-file", "", "Path to a JSON file containing pre-cached gh-aw-manifests (map[lockFile]*GHAWManifest); used by the MCP server to supply a tamper-proof manifest baseline captured at startup") compileCmd.Flags().Bool("ghes", false, "Enable GitHub Enterprise Server (GHES) compatibility mode: emit upload-artifact@v3 and download-artifact@v3 instead of the latest v7/v8 which are not supported on GHES. Overrides the aw.json ghes field") @@ -787,12 +787,12 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all runCmd.Flags().StringP("engine", "e", "", "Override AI engine (copilot, claude, codex, gemini, crush)") runCmd.Flags().StringP("repo", "r", "", "Target repository ([HOST/]owner/repo format). Defaults to current repository") runCmd.Flags().String("ref", "", "Branch or tag name to run the workflow on (default: current branch)") - runCmd.Flags().Bool("auto-merge-prs", false, "Auto-merge any pull requests created during the workflow execution") + runCmd.Flags().Bool("auto-merge-prs", false, "Auto-merge any pull requests created during execution") runCmd.Flags().StringArrayP("raw-field", "F", []string{}, "Add a string parameter in key=value format (can be used multiple times)") runCmd.Flags().Bool("push", false, "Commit and push workflow files (including transitive imports) before running") runCmd.Flags().Bool("dry-run", false, "Validate workflow without actually triggering execution on GitHub Actions") runCmd.Flags().BoolP("json", "j", false, "Output results in JSON format") - runCmd.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") + runCmd.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.") // Register completions for run command runCmd.ValidArgsFunction = cli.CompleteWorkflowNames cli.RegisterEngineFlagCompletion(runCmd) diff --git a/docs/adr/42832-cli-flag-naming-convention-no-x-and-wizard-parity.md b/docs/adr/42832-cli-flag-naming-convention-no-x-and-wizard-parity.md new file mode 100644 index 00000000000..5041dc584d5 --- /dev/null +++ b/docs/adr/42832-cli-flag-naming-convention-no-x-and-wizard-parity.md @@ -0,0 +1,52 @@ +# ADR-42832: Adopt `--no-X` Flag Convention and Enforce `add-wizard` Flag Parity + +**Date**: 2026-07-02 +**Status**: Draft +**Deciders**: pelikhan (automated inspection via copilot-swe-agent) + +--- + +### Context + +The `gh-aw` CLI had grown inconsistently: most boolean negation flags followed a `--no-X` prefix (e.g., `--no-fix`, `--no-compile`, `--no-actions`), but one flag — `--disable-codemod` — used a `--disable-X` prefix introduced at a different time. This made the CLI surface feel non-uniform to users who relied on tab-completion and `--help` output. + +Separately, the `add-wizard` command shared the same underlying workflow installation logic as `add` and `deploy`, but its `AddInteractiveConfig` struct was missing the `Name`, `Force`, `AppendText`, and `DisableSecurityScanner` fields. Those fields were hardcoded to zero values when `add-wizard` called into `AddResolvedWorkflows`, silently ignoring flags a user might expect to work by analogy with `add`/`deploy`. + +An automated CLI consistency inspection across 29 files identified these as the two highest-severity issues in the codebase's CLI surface. + +### Decision + +We will rename `--disable-codemod` to `--no-codemod` in the `fix` and `upgrade` commands, matching the `--no-X` convention used everywhere else. The old flag name is not preserved as an alias (a clean break). We will also expand `AddInteractiveConfig` to include `Name`, `Force`, `AppendText`, and `DisableSecurityScanner`, register those flags on `add-wizard`, and propagate them through to `AddResolvedWorkflows` so the interactive wizard has full parity with the direct `add`/`deploy` commands. + +### Alternatives Considered + +#### Alternative 1: Keep `--disable-codemod` and add `--no-codemod` as an alias + +Both flag names would be accepted. The inconsistency would remain visible in `--help` output and completion lists, but existing scripts using `--disable-codemod` would not break. Rejected because maintaining two names for the same flag adds ongoing documentation burden and extends the period of inconsistency indefinitely. + +#### Alternative 2: Keep `--disable-codemod` unchanged and only fix documentation + +Leave the flag name as-is and update only prose that mentioned it. This eliminates the breakage risk but permanently encodes the exception into the CLI contract. Rejected because the `--disable-X` outlier will confuse future contributors adding new flags, who will need to decide which convention to follow. + +#### Alternative 3: Extend `AddInteractiveConfig` but not register the new flags on `add-wizard` + +Propagate the struct fields but do not expose the flags at the CLI layer, deferring the UX decision. Rejected because half-wired flags (struct fields with no CLI entry point) are worse than no fields at all — they create dead code and false impressions that the feature is accessible. + +### Consequences + +#### Positive +- The CLI flag surface is now internally consistent: all boolean negation flags use `--no-X`. +- `add-wizard` users can now pass `--name`, `--force`, `--append`, and `--no-security-scanner` with the same semantics as `add`/`deploy`. +- The `AddInteractiveConfig` struct accurately reflects the full set of options the wizard supports, making the code easier to audit and extend. + +#### Negative +- Renaming `--disable-codemod` to `--no-codemod` is a breaking change for any scripts or CI configurations that used the old flag name. No deprecation alias was added. +- `add-wizard` now accepts more flags, which increases its test surface and the risk of future divergence if `add`/`deploy` flags are added without a corresponding update to `add-wizard`. + +#### Neutral +- Downstream documentation (docs/src/content/docs/setup/cli.md) was updated in the same PR to reflect the renamed flag and the corrected `--push` behaviour description. +- The `disable-security-scanner` legacy alias on `add`, `deploy`, and `trial` is preserved as a hidden flag; this PR did not extend that pattern to `add-wizard`'s new `--no-security-scanner` flag. + +--- + +*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.* diff --git a/docs/src/content/docs/setup/cli.md b/docs/src/content/docs/setup/cli.md index 419afdcdd2a..6281dcd3dc6 100644 --- a/docs/src/content/docs/setup/cli.md +++ b/docs/src/content/docs/setup/cli.md @@ -120,7 +120,7 @@ Use `gh aw version` to print the current version. ### The `--push` Flag -`gh aw run --push` stages all changes, commits them, and pushes before dispatching the workflow. It requires a clean working directory. +`gh aw run --push` stages workflow files only (including transitive imports), commits them, and pushes before dispatching the workflow. It does not require a clean working directory, but will abort if there are already staged files that are not part of the workflow set — commit or unstage those files before using `--push`. For `init`, `update`, and `upgrade`, use `--create-pull-request` instead. @@ -140,7 +140,7 @@ gh aw init --engine claude # Skip Copilot-specific artifacts gh aw init --no-mcp # Skip MCP server integration gh aw init --no-skill # Skip dispatcher skill creation gh aw init --no-agent # Skip custom agent creation -gh aw init --codespaces "" # Configure Codespaces for current repo only +gh aw init --codespaces "" # Configure Codespaces for current repository only gh aw init --codespaces repo1,repo2 # Configure Codespaces with additional repos gh aw init --completions # Install shell completions gh aw init --create-pull-request # Initialize and open a pull request @@ -265,9 +265,9 @@ gh aw fix my-workflow --write # Fix specific workflow gh aw fix --list-codemods # List available codemods ``` -**Options:** `--dir/-d`, `--disable-codemod`, `--list-codemods`, `--write` +**Options:** `--dir/-d`, `--no-codemod`, `--list-codemods`, `--write` -Use `--disable-codemod` (repeatable) to skip specific codemod IDs by name. +Use `--no-codemod` (repeatable) to skip specific codemod IDs by name. Available codemods include: @@ -708,11 +708,11 @@ gh aw upgrade --audit --json # Dependency audit in JSON format gh aw upgrade --org my-org --create-issue --yes # Auto-accept per-repo confirmations (required in CI) ``` -**Options:** `--dir/-d`, `--no-fix`, `--no-actions`, `--no-compile`, `--disable-codemod`, `--create-pull-request`, `--create-issue`, `--org`, `--repos`, `--yes/-y`, `--audit`, `--json/-j`, `--approve`, `--pre-releases` +**Options:** `--dir/-d`, `--no-fix`, `--no-actions`, `--no-compile`, `--no-codemod`, `--create-pull-request`, `--create-issue`, `--org`, `--repos`, `--yes/-y`, `--audit`, `--json/-j`, `--approve`, `--pre-releases` Org mode (`--org`) previews or creates upgrade pull requests across every repository in an organization. Use `--repos` to limit org mode to repositories matching one or more glob patterns, `--create-issue` to open an issue in each org repository with agentic workflows (requires `--org`), and `--yes/-y` to auto-accept org-mode create confirmations (required in CI). -Use `--disable-codemod` (repeatable) to skip specific codemod IDs during the embedded fix step. This flag is ignored when `--no-fix` is set. +Use `--no-codemod` (repeatable) to skip specific codemod IDs during the embedded fix step. This flag is ignored when `--no-fix` is set. #### `env` @@ -822,7 +822,7 @@ gh aw completion fish # Generate fish script gh aw completion powershell # Generate powershell script ``` -**Subcommands:** `install`, `uninstall`, `bash`, `zsh`, `fish`, `powershell`. See [Shell Completions](#shell-completions). +**Subcommands:** `install`, `uninstall`. **Shell values:** `bash`, `zsh`, `fish`, `powershell`. See [Shell Completions](#shell-completions). #### `project` @@ -830,7 +830,7 @@ Create and manage GitHub Projects V2 boards. ##### `project new` -Create a new GitHub Project V2 owned by a user or organization with optional repository linking. +Create a new GitHub Projects V2 board owned by a user or organization with optional repository linking. ```bash wrap gh aw project new "My Project" --owner @me # Create user project diff --git a/pkg/cli/add_command.go b/pkg/cli/add_command.go index 732b2ad264a..9e114227ced 100644 --- a/pkg/cli/add_command.go +++ b/pkg/cli/add_command.go @@ -32,7 +32,7 @@ Workflow specifications: - Four+ parts: "owner/repo/workflows/workflow-name.md[@version]" (requires explicit .md extension) - GitHub URL: "https://github.com/owner/repo/blob/branch/path/to/workflow.md" - Arbitrary URL: "https://example.com/workflow.md" (fetches and dispatches on Content-Type) - - text/markdown → treated as a gh-aw workflow markdown file + - text/markdown → treated as a gh-aw workflow Markdown file - application/json → converted from a JSON workflow definition - Local file: "./path/to/workflow.md" (adds a workflow from local filesystem) - Local wildcard: "./*.md" or "./dir/*.md" (adds all .md files matching pattern) @@ -179,7 +179,7 @@ func registerAddCommandFlags(cmd *cobra.Command) { cmd.Flags().BoolP("force", "f", false, "Overwrite existing workflow files without confirmation") // Add append flag to add command - cmd.Flags().String("append", "", "Append extra content to the end of agentic workflow on installation") + cmd.Flags().String("append", "", "Append extra content to the end of an agentic workflow on installation") // Add no-gitattributes flag to add command cmd.Flags().Bool("no-gitattributes", false, "Skip updating .gitattributes file") @@ -194,8 +194,8 @@ 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 an undocumented alias) - cmd.Flags().Bool("no-security-scanner", false, "Disable security scanning of workflow markdown content") - cmd.Flags().Bool("disable-security-scanner", false, "Disable security scanning of workflow markdown content") + cmd.Flags().Bool("no-security-scanner", false, "Disable security scanning of workflow Markdown content") + cmd.Flags().Bool("disable-security-scanner", false, "Disable security scanning of workflow Markdown content") _ = cmd.Flags().MarkHidden("disable-security-scanner") // Register completions for add command diff --git a/pkg/cli/add_interactive_git.go b/pkg/cli/add_interactive_git.go index 90d033cdb1a..0eb3568c957 100644 --- a/pkg/cli/add_interactive_git.go +++ b/pkg/cli/add_interactive_git.go @@ -37,15 +37,15 @@ func (c *AddInteractiveConfig) createWorkflowPRAndConfigureSecret(ctx context.Co Verbose: c.Verbose, Quiet: true, EngineOverride: c.EngineOverride, - Name: "", - Force: false, - AppendText: "", + Name: c.Name, + Force: c.Force, + AppendText: c.AppendText, CreatePR: true, NoGitattributes: c.NoGitattributes, WorkflowDir: c.WorkflowDir, NoStopAfter: c.NoStopAfter, StopAfter: c.StopAfter, - DisableSecurityScanner: false, + DisableSecurityScanner: c.DisableSecurityScanner, AddCopilotRequestsPermission: c.UseCopilotRequests, } result, err := AddResolvedWorkflows(ctx, c.WorkflowSpecs, c.resolvedWorkflows, opts) diff --git a/pkg/cli/add_interactive_orchestrator.go b/pkg/cli/add_interactive_orchestrator.go index 241fbe5a530..35f89483713 100644 --- a/pkg/cli/add_interactive_orchestrator.go +++ b/pkg/cli/add_interactive_orchestrator.go @@ -19,17 +19,21 @@ var addInteractiveLog = logger.New("cli:add_interactive") // AddInteractiveConfig holds configuration for interactive add mode type AddInteractiveConfig struct { - Ctx context.Context // Context for cancellation (Ctrl-C handling) - WorkflowSpecs []string - Verbose bool - EngineOverride string - NoGitattributes bool - WorkflowDir string - NoStopAfter bool - StopAfter string - SkipWorkflowRun bool - SkipSecret bool // Skip the API secret prompt (useful when secret is set at org level) - RepoOverride string // owner/repo format, if user provides it + Ctx context.Context // Context for cancellation (Ctrl-C handling) + WorkflowSpecs []string + Verbose bool + EngineOverride string + Name string + Force bool + AppendText string + NoGitattributes bool + WorkflowDir string + NoStopAfter bool + StopAfter string + DisableSecurityScanner bool + SkipWorkflowRun bool + SkipSecret bool // Skip the API secret prompt (useful when secret is set at org level) + RepoOverride string // owner/repo format, if user provides it // UseCopilotRequests indicates the user chose org-billing (copilot-requests) auth // instead of a PAT when setting up the Copilot engine during the wizard. diff --git a/pkg/cli/add_wizard_command.go b/pkg/cli/add_wizard_command.go index 02bbc4ae348..b17cd033981 100644 --- a/pkg/cli/add_wizard_command.go +++ b/pkg/cli/add_wizard_command.go @@ -20,7 +20,7 @@ func NewAddWizardCommand(validateEngine func(string) error) *cobra.Command { Long: `Interactively add one or more agentic workflows with guided setup. This command walks you through: - - Selecting an AI engine (Copilot, Claude, Codex, Gemini, or Crush) + - Selecting an AI engine (copilot, claude, codex, gemini, or crush) - Configuring API keys and secrets - Creating a pull request with the workflow - Optionally running the workflow immediately @@ -34,7 +34,7 @@ Workflow specifications: - Four+ parts: "owner/repo/workflows/workflow-name.md[@version]" (requires explicit .md extension) - GitHub URL: "https://github.com/owner/repo/blob/branch/path/to/workflow.md" - Arbitrary URL: "https://example.com/workflow.md" (fetches and dispatches on Content-Type) - - text/markdown → treated as a gh-aw workflow markdown file + - text/markdown → treated as a gh-aw workflow Markdown file - application/json → converted from a JSON workflow definition - Local file: "./path/to/workflow.md" - Version can be tag, branch, or SHA (for remote workflows) @@ -63,17 +63,26 @@ Note: To create a new workflow from scratch, use the 'new' command instead.`, RunE: func(cmd *cobra.Command, args []string) error { workflows := args engineOverride, _ := cmd.Flags().GetString("engine") + nameFlag, _ := cmd.Flags().GetString("name") + forceFlag, _ := cmd.Flags().GetBool("force") + appendText, _ := cmd.Flags().GetString("append") verbose, _ := cmd.Flags().GetBool("verbose") noGitattributes, _ := cmd.Flags().GetBool("no-gitattributes") 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 noSecret, _ := cmd.Flags().GetBool("no-secret") skipSecretLegacy, _ := cmd.Flags().GetBool("skip-secret") skipSecret := noSecret || skipSecretLegacy addWizardLog.Printf("Starting add-wizard: workflows=%v, engine=%s, verbose=%v", workflows, engineOverride, verbose) + if nameFlag != "" && len(workflows) > 1 { + return errors.New("--name flag cannot be used when adding multiple workflows at once") + } if err := validateEngine(engineOverride); err != nil { return err } @@ -87,21 +96,34 @@ Note: To create a new workflow from scratch, use the 'new' command instead.`, } return RunAddInteractive(cmd.Context(), &AddInteractiveConfig{ - WorkflowSpecs: workflows, - Verbose: verbose, - EngineOverride: engineOverride, - NoGitattributes: noGitattributes, - WorkflowDir: workflowDir, - NoStopAfter: noStopAfter, - StopAfter: stopAfter, - SkipSecret: skipSecret, + WorkflowSpecs: workflows, + Verbose: verbose, + EngineOverride: engineOverride, + Name: nameFlag, + Force: forceFlag, + AppendText: appendText, + NoGitattributes: noGitattributes, + WorkflowDir: workflowDir, + NoStopAfter: noStopAfter, + StopAfter: stopAfter, + DisableSecurityScanner: disableSecurityScanner, + SkipSecret: skipSecret, }) }, } + // Add name flag + cmd.Flags().StringP("name", "n", "", "Specify name for the added workflow (without .md extension)") + // Add AI engine flag addEngineFlag(cmd) + // Add force flag + cmd.Flags().BoolP("force", "f", false, "Overwrite existing workflow files without confirmation") + + // Add append flag + cmd.Flags().String("append", "", "Append extra content to the end of an agentic workflow on installation") + // Add no-gitattributes flag cmd.Flags().Bool("no-gitattributes", false, "Skip updating .gitattributes file") @@ -114,6 +136,11 @@ Note: To create a new workflow from scratch, use the 'new' command instead.`, // Add stop-after flag 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 (--disable-security-scanner is kept as an undocumented alias) + cmd.Flags().Bool("no-security-scanner", false, "Disable security scanning of workflow Markdown content") + cmd.Flags().Bool("disable-security-scanner", false, "Disable security scanning of workflow Markdown content") + _ = cmd.Flags().MarkHidden("disable-security-scanner") + // Add no-secret flag (--skip-secret is kept as an undocumented alias) cmd.Flags().Bool("no-secret", false, "Skip the API secret prompt (use when the secret is already set at the org or repo level)") cmd.Flags().Bool("skip-secret", false, "Skip the API secret prompt (use when the secret is already set at the org or repo level)") diff --git a/pkg/cli/add_wizard_command_test.go b/pkg/cli/add_wizard_command_test.go index 1dd541724d4..d5e22bab922 100644 --- a/pkg/cli/add_wizard_command_test.go +++ b/pkg/cli/add_wizard_command_test.go @@ -12,7 +12,7 @@ import ( func TestAddWizardCommandMentionsCrush(t *testing.T) { cmd := NewAddWizardCommand(func(string) error { return nil }) require.NotNil(t, cmd, "Add wizard command should be created") - assert.Contains(t, cmd.Long, "Copilot, Claude, Codex, Gemini, or Crush", "Add wizard help should mention all interactive engine options") + assert.Contains(t, cmd.Long, "copilot, claude, codex, gemini, or crush", "Add wizard help should mention all interactive engine options") } func TestAddWizardCommand_UsesStandardThreePartWorkflowSpecWording(t *testing.T) { @@ -24,3 +24,37 @@ func TestAddWizardCommand_UsesStandardThreePartWorkflowSpecWording(t *testing.T) assert.Contains(t, cmd.Long, "For github/*, githubnext/*, and microsoft/* sources, shorthand resolves on github.com.") assert.Contains(t, cmd.Long, "Use full https://github.com/... source URLs for other public github.com workflows.") } + +func TestAddWizardCommand_NameFlagRejectsMultipleWorkflows(t *testing.T) { + cmd := NewAddWizardCommand(func(string) error { return nil }) + require.NotNil(t, cmd) + cmd.SetArgs([]string{"workflow-one", "workflow-two", "--name", "custom-name"}) + + err := cmd.Execute() + require.Error(t, err) + assert.EqualError(t, err, "--name flag cannot be used when adding multiple workflows at once") +} + +func TestAddWizardCommand_HasForceFlag(t *testing.T) { + cmd := NewAddWizardCommand(func(string) error { return nil }) + flag := cmd.Flags().Lookup("force") + require.NotNil(t, flag, "add-wizard should register --force") + assert.Equal(t, "bool", flag.Value.Type()) + shortFlag := cmd.Flags().ShorthandLookup("f") + require.NotNil(t, shortFlag, "-f shorthand should be registered") + assert.Equal(t, "force", shortFlag.Name) +} + +func TestAddWizardCommand_HasAppendFlag(t *testing.T) { + cmd := NewAddWizardCommand(func(string) error { return nil }) + flag := cmd.Flags().Lookup("append") + require.NotNil(t, flag, "add-wizard should register --append") + assert.Equal(t, "string", flag.Value.Type()) +} + +func TestAddWizardCommand_HasNoSecurityScannerFlag(t *testing.T) { + cmd := NewAddWizardCommand(func(string) error { return nil }) + flag := cmd.Flags().Lookup("no-security-scanner") + require.NotNil(t, flag, "add-wizard should register --no-security-scanner") + assert.Equal(t, "bool", flag.Value.Type()) +} diff --git a/pkg/cli/deploy_command.go b/pkg/cli/deploy_command.go index 03615e98ccc..c25ec613f4f 100644 --- a/pkg/cli/deploy_command.go +++ b/pkg/cli/deploy_command.go @@ -30,7 +30,7 @@ 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.`, +The command clones the target repository, updates existing workflows from source, adds the specified workflows, recompiles .lock.yml 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 @@ -90,17 +90,17 @@ func validateDeployArgs(cmd *cobra.Command, args []string) error { } func registerDeployFlags(cmd *cobra.Command) { - cmd.Flags().StringP("repo", "r", "", "Target repository in [HOST/]owner/repo format (required unless --org is provided)") + cmd.Flags().StringP("repo", "r", "", "Target repository ([HOST/]owner/repo format; required unless --org is provided)") cmd.Flags().StringP("name", "n", "", "Specify name for the added workflow (without .md extension)") addEngineFlag(cmd) cmd.Flags().BoolP("force", "f", false, "Overwrite existing workflow files without confirmation") - cmd.Flags().String("append", "", "Append extra content to the end of agentic workflow on installation") + cmd.Flags().String("append", "", "Append extra content to the end of an agentic workflow on installation") cmd.Flags().Bool("no-gitattributes", false, "Skip updating .gitattributes file") cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: .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, "Disable security scanning of workflow markdown content") - cmd.Flags().Bool("disable-security-scanner", false, "Disable security scanning of workflow markdown content") + cmd.Flags().Bool("no-security-scanner", false, "Disable security scanning of workflow Markdown content") + cmd.Flags().Bool("disable-security-scanner", false, "Disable security scanning of workflow Markdown content") _ = cmd.Flags().MarkHidden("disable-security-scanner") cmd.Flags().String("cool-down", defaultDeployCooldown, coolDownFlagUsage) cmd.Flags().String("org", "", "Deploy workflows across repositories in an organization") diff --git a/pkg/cli/env_command.go b/pkg/cli/env_command.go index c105d8cc001..6cd8de5053a 100644 --- a/pkg/cli/env_command.go +++ b/pkg/cli/env_command.go @@ -114,7 +114,7 @@ func NewEnvCommand() *cobra.Command { cmd := &cobra.Command{ Use: "env", Short: "Manage compiler defaults as GitHub variables", - Long: `Manage compiler default variables in batch for repository, organization, or enterprise scope. + Long: `Manage compiler default variables in bulk for repository, organization, or enterprise scope. The YAML file is flat and uses default_-prefixed lowercase keys (e.g., default_max_turns). Set a field to null (or omit it) in update mode to delete the variable from the selected scope. diff --git a/pkg/cli/fix_command.go b/pkg/cli/fix_command.go index 7e83abb438c..135b95f5eb6 100644 --- a/pkg/cli/fix_command.go +++ b/pkg/cli/fix_command.go @@ -69,7 +69,9 @@ all steps and additionally: write, _ := cmd.Flags().GetBool("write") verbose, _ := cmd.Flags().GetBool("verbose") dir, _ := cmd.Flags().GetString("dir") - disabledCodemods, _ := cmd.Flags().GetStringSlice("disable-codemod") + disabledCodemods, _ := cmd.Flags().GetStringSlice("no-codemod") + disabledCodemodsLegacy, _ := cmd.Flags().GetStringSlice("disable-codemod") + disabledCodemods = append(disabledCodemods, disabledCodemodsLegacy...) if listCodemods { return listAvailableCodemods() @@ -82,7 +84,9 @@ all steps and additionally: cmd.Flags().Bool("write", false, "Write changes to files (without this flag, no changes are made)") cmd.Flags().Bool("list-codemods", false, "List all available codemods and exit") cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: .github/workflows)") + cmd.Flags().StringSlice("no-codemod", nil, "Disable specific codemod IDs during the fix step (repeatable)") cmd.Flags().StringSlice("disable-codemod", nil, "Disable specific codemod IDs during the fix step (repeatable)") + _ = cmd.Flags().MarkHidden("disable-codemod") // Register completions cmd.ValidArgsFunction = CompleteWorkflowNames diff --git a/pkg/cli/fix_command_test.go b/pkg/cli/fix_command_test.go index 934f136d0b1..033a0e1dfd6 100644 --- a/pkg/cli/fix_command_test.go +++ b/pkg/cli/fix_command_test.go @@ -516,12 +516,12 @@ func TestGetAllCodemods(t *testing.T) { } } -func TestNewFixCommand_HasDisableCodemodFlag(t *testing.T) { +func TestNewFixCommand_HasNoCodemodFlag(t *testing.T) { cmd := NewFixCommand() require.NotNil(t, cmd) - flag := cmd.Flags().Lookup("disable-codemod") - require.NotNil(t, flag, "fix command should register --disable-codemod") + flag := cmd.Flags().Lookup("no-codemod") + require.NotNil(t, flag, "fix command should register --no-codemod") assert.Equal(t, "stringSlice", flag.Value.Type()) assert.Contains(t, flag.Usage, "Disable specific codemod IDs") } diff --git a/pkg/cli/flags_test.go b/pkg/cli/flags_test.go index df94a2e5f0a..851f4c6a6b8 100644 --- a/pkg/cli/flags_test.go +++ b/pkg/cli/flags_test.go @@ -67,6 +67,40 @@ func TestShortFlagConsistency(t *testing.T) { shouldExist: true, description: "compile should have force short flag", }, + { + name: "add-wizard command has -f for --force", + shortFlag: "f", + longFlag: "force", + commandSetup: func() *cobra.Command { return NewAddWizardCommand(func(string) error { return nil }) }, + shouldExist: true, + description: "add-wizard should have force short flag", + }, + + // -n flag (name) + { + name: "add command has -n for --name", + shortFlag: "n", + longFlag: "name", + commandSetup: func() *cobra.Command { return NewAddCommand(validateEngineStub) }, + shouldExist: true, + description: "add should have name short flag", + }, + { + name: "deploy command has -n for --name", + shortFlag: "n", + longFlag: "name", + commandSetup: func() *cobra.Command { return NewDeployCommand(validateEngineStub) }, + shouldExist: true, + description: "deploy should have name short flag", + }, + { + name: "add-wizard command has -n for --name", + shortFlag: "n", + longFlag: "name", + commandSetup: func() *cobra.Command { return NewAddWizardCommand(func(string) error { return nil }) }, + shouldExist: true, + description: "add-wizard should have name short flag", + }, // -F flag (raw-field in run command) { @@ -267,7 +301,7 @@ func createCompileCommandStub() *cobra.Command { func createNewCommandStub() *cobra.Command { cmd := &cobra.Command{Use: "new"} - cmd.Flags().BoolP("force", "f", false, "Overwrite existing files") + cmd.Flags().BoolP("force", "f", false, "Overwrite existing workflow files") cmd.Flags().BoolP("interactive", "i", false, "Launch interactive wizard") return cmd } diff --git a/pkg/cli/forecast_command.go b/pkg/cli/forecast_command.go index 40e8839368a..636592a56da 100644 --- a/pkg/cli/forecast_command.go +++ b/pkg/cli/forecast_command.go @@ -39,7 +39,7 @@ func NewForecastCommand() *cobra.Command { Long: `[EXPERIMENTAL] Forecast AI Credit (AIC) usage for agentic workflows by sampling recent run history and projecting forward on a per-week or per-month basis. -The forecaster downloads a sample of recent completed workflow runs and derives +The forecaster downloads a sample of recently completed workflow runs and derives per-run metrics (AIC, duration, success rate). When runs have been previously processed by 'gh aw logs', cached token-usage data is used. The observed run frequency is then projected to the target period using a Monte Carlo diff --git a/pkg/cli/hash_command.go b/pkg/cli/hash_command.go index f5be6acac6e..1a0c1078259 100644 --- a/pkg/cli/hash_command.go +++ b/pkg/cli/hash_command.go @@ -22,7 +22,7 @@ func NewHashCommand() *cobra.Command { The hash includes: - All frontmatter fields from the main workflow - Frontmatter from all imported workflows (BFS traversal) -- Template expressions containing env. or vars. from the markdown body +- Template expressions containing env. or vars. from the Markdown body - Version information (gh-aw, awf, agents) The hash can be used to detect configuration changes between compilation and execution.`, diff --git a/pkg/cli/health_command.go b/pkg/cli/health_command.go index effc0f52fd6..9e04c1ab201 100644 --- a/pkg/cli/health_command.go +++ b/pkg/cli/health_command.go @@ -37,7 +37,7 @@ func NewHealthCommand() *cobra.Command { Long: `Display workflow health metrics, success rates, and execution trends. Shows health metrics for workflows including: -- Success/failure rates over time period +- Success/failure rates over a time period - Trend indicators (↑ improving, → stable, ↓ degrading) - Average execution duration - Warnings when success rate drops below threshold diff --git a/pkg/cli/init_command.go b/pkg/cli/init_command.go index fb7c1cdf3b9..63bf087ac75 100644 --- a/pkg/cli/init_command.go +++ b/pkg/cli/init_command.go @@ -30,7 +30,7 @@ This command: - Creates the custom agent at .github/agents/agentic-workflows.md - Removes old prompt files from .github/prompts/ if they exist - Configures VSCode settings (.vscode/settings.json) -- Generates/updates .github/workflows/agentics-maintenance.yml if any workflows use expires field for discussions or issues +- Generates/updates .github/workflows/agentics-maintenance.yml if any workflows use the expires field for discussions or issues By default (without --no-mcp): - Creates .github/workflows/copilot-setup-steps.yml with gh-aw installation steps @@ -50,7 +50,7 @@ With --codespaces flag: - Configures permissions for current repo: actions:write, contents:write, discussions:read, issues:read, pull-requests:write, workflows:write - Configures permissions for additional repos (in same org): actions:read, contents:read, discussions:read, issues:read, pull-requests:read, workflows:read - Adds GitHub Copilot extensions and gh aw CLI installation -- Use with an empty value (--codespaces "") for current repo only, or with comma-separated repos (--codespaces repo1,repo2) +- Use with an empty value (--codespaces "") for current repository only, or with comma-separated repos (--codespaces repo1,repo2) With --completions flag: - Automatically detects your shell (bash, zsh, fish, or PowerShell) @@ -68,7 +68,7 @@ After running this command, you can: ` + string(constants.CLIExtensionPrefix) + ` init --no-mcp # Skip MCP configuration ` + string(constants.CLIExtensionPrefix) + ` init --no-skill # Skip dispatcher skill creation ` + string(constants.CLIExtensionPrefix) + ` init --no-agent # Skip custom agent creation - ` + string(constants.CLIExtensionPrefix) + ` init --codespaces "" # Configure Codespaces for current repo only + ` + string(constants.CLIExtensionPrefix) + ` init --codespaces "" # Configure Codespaces for current repository only ` + string(constants.CLIExtensionPrefix) + ` init --codespaces repo1,repo2 # Codespaces with additional repos ` + string(constants.CLIExtensionPrefix) + ` init --completions # Install shell completions ` + string(constants.CLIExtensionPrefix) + ` init --create-pull-request # Initialize and create a pull request`, @@ -143,7 +143,7 @@ After running this command, you can: cmd.Flags().Bool("no-mcp", false, "Skip configuring gh-aw MCP server integration for GitHub Copilot Agent") cmd.Flags().Bool("no-skill", false, "Skip creating the agentic-workflows dispatcher skill") cmd.Flags().Bool("no-agent", false, "Skip creating the Agentic Workflows custom agent") - cmd.Flags().String("codespaces", "", "Create devcontainer.json for GitHub Codespaces with agentic workflows support. Specify comma-separated repository names in the same organization (e.g., repo1,repo2), or use with an empty value for the current repo only") + cmd.Flags().String("codespaces", "", "Create devcontainer.json for GitHub Codespaces with agentic workflows support. Specify comma-separated repository names in the same organization (e.g., repo1,repo2), or use with an empty value for the current repository only") cmd.Flags().Bool("completions", false, "Install shell completion for the detected shell (bash, zsh, fish, or PowerShell)") cmd.Flags().Bool("create-pull-request", false, "Create a pull request with the initialization changes") cmd.Flags().Bool("pr", false, "Alias for --create-pull-request") diff --git a/pkg/cli/init_command_test.go b/pkg/cli/init_command_test.go index 60be811100a..c8b1a81521a 100644 --- a/pkg/cli/init_command_test.go +++ b/pkg/cli/init_command_test.go @@ -92,7 +92,7 @@ func TestNewInitCommand(t *testing.T) { t.Error("Expected 'codespaces' flag to be defined") return } - if !strings.Contains(codespaceFlag.Usage, "or use with an empty value for the current repo only") { + if !strings.Contains(codespaceFlag.Usage, "or use with an empty value for the current repository only") { t.Errorf("Expected codespaces flag help text to include article fixes, got %q", codespaceFlag.Usage) } diff --git a/pkg/cli/lint_command.go b/pkg/cli/lint_command.go index 1a138c40858..491bb6adaa3 100644 --- a/pkg/cli/lint_command.go +++ b/pkg/cli/lint_command.go @@ -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", constants.GetWorkflowDir(), "Workflow directory (used when no arguments are provided)") cmd.Flags().Bool("shellcheck", false, "Enable shellcheck integration in actionlint") cmd.Flags().Bool("pyflakes", false, "Enable pyflakes integration in actionlint") diff --git a/pkg/cli/logs_command.go b/pkg/cli/logs_command.go index f0d68d22963..328c3d56ba4 100644 --- a/pkg/cli/logs_command.go +++ b/pkg/cli/logs_command.go @@ -39,7 +39,7 @@ This command fetches workflow runs, downloads their artifacts, and extracts them organized folders named by run ID. It also provides an overview table with aggregate metrics including duration, token usage, and cost information. -By default only the compact usage artifact is downloaded (token usage, run metadata). +By default, only the compact usage artifact is downloaded (token usage, run metadata). Use --artifacts all to download all artifacts, or specify individual sets such as --artifacts agent,firewall to fetch only what you need. diff --git a/pkg/cli/mcp_list_tools.go b/pkg/cli/mcp_list_tools.go index 4fe38327e13..4fa8f3a9498 100644 --- a/pkg/cli/mcp_list_tools.go +++ b/pkg/cli/mcp_list_tools.go @@ -172,8 +172,8 @@ func NewMCPListToolsSubcommand() *cobra.Command { cmd := &cobra.Command{ Use: "list-tools [workflow]", - Short: "List available tools for a specific MCP server", - Long: `List available tools for a specific MCP server. + Short: "List available tools for a specific MCP server, or find workflows using an MCP server", + Long: `List available tools for a specific MCP server, or find workflows using an MCP server. This command connects to the specified MCP server and displays all available tools. It reuses the same infrastructure as 'mcp inspect' to establish connections and diff --git a/pkg/cli/mcp_list_tools_test.go b/pkg/cli/mcp_list_tools_test.go index d2b1ee53254..2743a9e0350 100644 --- a/pkg/cli/mcp_list_tools_test.go +++ b/pkg/cli/mcp_list_tools_test.go @@ -328,7 +328,7 @@ func TestNewMCPListToolsSubcommand(t *testing.T) { t.Errorf("Expected Use to be 'list-tools [workflow]', got: %s", cmd.Use) } - if cmd.Short != "List available tools for a specific MCP server" { + if cmd.Short != "List available tools for a specific MCP server, or find workflows using an MCP server" { t.Errorf("Expected Short description, got: %s", cmd.Short) } diff --git a/pkg/cli/outcomes_command.go b/pkg/cli/outcomes_command.go index 304690cd8e7..55c016c06ac 100644 --- a/pkg/cli/outcomes_command.go +++ b/pkg/cli/outcomes_command.go @@ -60,7 +60,7 @@ This answers the question: "Did this workflow's actions actually help?"`, addJSONFlag(cmd) addRepoFlag(cmd) - addOutputFlag(cmd, "") + addOutputFlag(cmd, defaultLogsOutputDir) cmd.Flags().String("outcomes-dir", "", "Write outcome JSONL to this directory for OTLP export") cmd.AddCommand(NewOutcomesHistorySubcommand()) diff --git a/pkg/cli/project_command.go b/pkg/cli/project_command.go index 06d1b334e2b..9a31599a9e3 100644 --- a/pkg/cli/project_command.go +++ b/pkg/cli/project_command.go @@ -37,14 +37,14 @@ func NewProjectCommand() *cobra.Command { Short: "Create GitHub Projects V2 boards", Long: `Create GitHub Projects V2 boards linked to repositories. -GitHub Projects V2 provides kanban-style project boards for tracking issues, +GitHub Projects V2 provides Kanban-style project boards for tracking issues, pull requests, and tasks across repositories. This command allows you to create new projects owned by users or organizations and optionally link them to specific repositories. Available subcommands: - - new - Create a new GitHub Project V2 board`, + - new - Create a new GitHub Projects V2 board`, Example: ` gh aw project new "My Project" --owner @me # Create user project gh aw project new "Team Board" --owner myorg # Create org project gh aw project new "Bugs" --owner myorg --link myorg/myrepo # Create and link to repo`, @@ -60,8 +60,8 @@ Available subcommands: func NewProjectNewCommand() *cobra.Command { cmd := &cobra.Command{ Use: "new