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
2 changes: 1 addition & 1 deletion .github/aw/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ gh aw run <workflow-name> # Run by short name
gh aw run <workflow-name>.md # Alternative: explicit .md extension
gh aw run <workflow-name> --ref main # Run on a specific branch/tag/SHA
gh aw run <workflow-name> --repeat 3 # Run 4 times total (1 + 3 repeats)
gh aw run <workflow-name> -F key=value # Pass a specific input (alias: --raw-field)
gh aw run <workflow-name> --raw-field key=value # Pass a specific input
```

**MCP equivalent**: Not available. Fallback: use the GitHub MCP server's `create_workflow_dispatch` with `workflow_id: <workflow-name>.lock.yml`.
Expand Down
23 changes: 14 additions & 9 deletions cmd/gh-aw/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Common Tasks:
` + string(constants.CLIExtensionPrefix) + ` compile # Compile all workflows
` + string(constants.CLIExtensionPrefix) + ` run my-workflow # Execute a workflow
` + string(constants.CLIExtensionPrefix) + ` status # Check workflow status
` + string(constants.CLIExtensionPrefix) + ` logs my-workflow # View execution logs
` + string(constants.CLIExtensionPrefix) + ` logs my-workflow # Download and analyze execution logs
` + string(constants.CLIExtensionPrefix) + ` audit <run-id-or-url> # Audit and compare workflow runs

For detailed help on any command, use:
Expand Down Expand Up @@ -173,17 +173,19 @@ 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.`,
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
by any workflow. Use --no-remove-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- --no-remove-orphans # Remove workflows but keep orphaned includes
` + string(constants.CLIExtensionPrefix) + ` remove my-workflow --dir .github/workflows/shared # Remove from custom directory`,
RunE: func(cmd *cobra.Command, args []string) error {
var pattern string
if len(args) > 0 {
pattern = args[0]
}
keepOrphans, _ := cmd.Flags().GetBool("keep-orphans")
noRemoveOrphans, _ := cmd.Flags().GetBool("no-remove-orphans")
keepOrphans = keepOrphans || noRemoveOrphans
workflowDir, _ := cmd.Flags().GetString("dir")
return cli.RemoveWorkflows(pattern, keepOrphans, workflowDir)
},
Expand Down Expand Up @@ -432,7 +434,7 @@ This command only works with workflows that have workflow_dispatch triggers.
` + string(constants.CLIExtensionPrefix) + ` run daily-perf-improver --repeat 3 # Run 4 times total (1 initial + 3 repeats)
` + string(constants.CLIExtensionPrefix) + ` run daily-perf-improver --enable-if-needed # Enable if disabled, run, then restore state
` + string(constants.CLIExtensionPrefix) + ` run daily-perf-improver --auto-merge-prs # Auto-merge any PRs created during execution
` + string(constants.CLIExtensionPrefix) + ` run daily-perf-improver -F name=value -F env=prod # Pass workflow inputs
` + string(constants.CLIExtensionPrefix) + ` run daily-perf-improver --raw-field name=value --raw-field env=prod # Pass workflow inputs
` + string(constants.CLIExtensionPrefix) + ` run daily-perf-improver --push # Commit, push, and dispatch the workflow
` + string(constants.CLIExtensionPrefix) + ` run daily-perf-improver --dry-run # Preview without triggering workflow runs
` + string(constants.CLIExtensionPrefix) + ` run daily-perf-improver --json # Output results in JSON format`,
Expand Down Expand Up @@ -711,7 +713,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", "", cli.EngineFlagOverrideUsage)
cli.RegisterEngineFlagCompletion(newCmd)
Expand Down Expand Up @@ -774,7 +776,9 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all
rootCmd.AddCommand(compileCmd)

// Add flags to remove command
removeCmd.Flags().Bool("no-remove-orphans", false, "Skip removal of orphaned include files that are no longer referenced by any workflow")
removeCmd.Flags().Bool("keep-orphans", false, "Skip removal of orphaned include files that are no longer referenced by any workflow")
_ = removeCmd.Flags().MarkDeprecated("keep-orphans", "use --no-remove-orphans instead")
removeCmd.Flags().StringP("dir", "d", "", "Workflow directory (default: $GH_AW_WORKFLOWS_DIR or .github/workflows)")
// Register completions for remove command
removeCmd.ValidArgsFunction = cli.CompleteWorkflowNames
Expand All @@ -795,10 +799,11 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all
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().StringArrayP("raw-field", "F", []string{}, "Pass a workflow dispatch input in key=value format (can be specified multiple times)")
runCmd.Flags().Bool("push", false, "Commit and push workflow files (including transitive imports) before running")
_ = runCmd.Flags().MarkShorthandDeprecated("raw-field", "use --raw-field instead")
runCmd.Flags().Bool("push", false, "Commit and push workflow files (including transitive imports) before running. Refuses to proceed when unrelated files are already staged.")
runCmd.Flags().Bool("dry-run", false, "Preview workflow execution without triggering runs on GitHub Actions")
runCmd.Flags().BoolP("json", "j", false, "Output results in JSON format")
runCmd.Flags().Bool("approve", false, "Approve safe update manifest changes when --push triggers an automatic recompile step")
runCmd.Flags().Bool("approve", false, "Approve safe update manifest changes when --push triggers an automatic recompile step. When strict mode is active (the default), the recompile step enforces safe update checking; pass this flag to approve those changes.")
// Register completions for run command
runCmd.ValidArgsFunction = cli.CompleteWorkflowNames
cli.RegisterEngineFlagCompletion(runCmd)
Expand Down
9 changes: 8 additions & 1 deletion cmd/gh-aw/main_help_text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@ func TestRunCommandHelpTextConsistency(t *testing.T) {
assert.Contains(t, runCmd.Long, "this command enters interactive mode and shows", "run command interactive mode text should be explicit")

runApprove := runCmd.Flags().Lookup("approve")
runPush := runCmd.Flags().Lookup("push")
runRawField := runCmd.Flags().Lookup("raw-field")
compileApprove := compileCmd.Flags().Lookup("approve")
require.NotNil(t, runApprove, "run command should define --approve")
require.NotNil(t, runPush, "run command should define --push")
require.NotNil(t, runRawField, "run command should define --raw-field")
require.NotNil(t, compileApprove, "compile command should define --approve")
assert.Contains(t, compileApprove.Usage, "safe update changes", "compile --approve should describe compiler safe update approval")
assert.Equal(t, "Approve safe update manifest changes when --push triggers an automatic recompile step", runApprove.Usage, "run --approve should explain the --push-triggered recompile behavior")
assert.Equal(t, "Approve safe update manifest changes when --push triggers an automatic recompile step. When strict mode is active (the default), the recompile step enforces safe update checking; pass this flag to approve those changes.", runApprove.Usage, "run --approve should explain the --push-triggered recompile behavior with strict mode context")
assert.Equal(t, "Commit and push workflow files (including transitive imports) before running. Refuses to proceed when unrelated files are already staged.", runPush.Usage, "run --push should describe the staged-files precondition precisely")
assert.Equal(t, "F", runRawField.Shorthand, "run --raw-field should keep the legacy -F shorthand for compatibility")
assert.Equal(t, "use --raw-field instead", runRawField.ShorthandDeprecated, "run -F shorthand should be marked deprecated")
}

func TestCompileScheduleSeedHelpUsesConsistentQuotes(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion docs/interactive-run-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ All standard `run` command flags work in interactive mode:
The following flags are NOT supported in interactive mode:
- `--repeat` - Use the displayed command for repeated runs
- `--enable-if-needed` - Enable workflows manually first
- `-F` / `--raw-field` - Inputs are collected interactively
- `--raw-field` - Inputs are collected interactively

## CI Detection

Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/experimental/trial-ops.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ gh aw trial githubnext/agentics/my-workflow \

```bash
gh aw trial ./my-workflow.md --delete-host-repo-after # Delete after completion
gh aw trial ./my-workflow.md --force-delete-host-repo-before # Clean slate before running
gh aw trial ./my-workflow.md --delete-host-repo-before # Clean slate before running
```

## Understanding Trial Results
Expand Down
20 changes: 10 additions & 10 deletions docs/src/content/docs/setup/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sidebar:
order: 200
---

The `gh aw` CLI extension enables developers to create, manage, and execute AI-powered workflows directly from the command line. It transforms natural language markdown files into GitHub Actions.
The `gh aw` CLI extension enables developers to create, manage, and execute AI-powered workflows directly from the command line. It transforms natural language Markdown files into GitHub Actions.

## Most Common Commands

Expand Down Expand Up @@ -133,7 +133,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 (including transitive imports), commits them, and pushes before dispatching the workflow. It refuses to proceed when unrelated files are already staged.

For `init`, `update`, and `upgrade`, use `--create-pull-request` instead.

Expand Down Expand Up @@ -178,7 +178,7 @@ When the Copilot engine is selected, the wizard prompts the user to choose an au

#### `add`

Add workflows from The Agentics collection or other repositories to `.github/workflows`. For remote workflows, this command follows frontmatter [`redirect`](/gh-aw/reference/frontmatter/#redirect-redirect) declarations before installation.
Add workflows from the Agentics collection or other repositories to `.github/workflows`. For remote workflows, this command follows frontmatter [`redirect`](/gh-aw/reference/frontmatter/#redirect-redirect) declarations before installation.

```bash wrap
gh aw add githubnext/agentics/ci-doctor # Add single workflow
Expand Down Expand Up @@ -341,7 +341,7 @@ Unlike `gh aw upgrade`, `gh aw compile` does not run codemods unless you pass `-

**Dependabot Integration (`--dependabot`):** Generates dependency manifests and `.github/dependabot.yml` by analyzing runtime tools across all workflows. See [Dependabot Support reference](/gh-aw/reference/dependabot/).

**Strict Mode (`--strict`):** Enforces security best practices: no write permissions (use [safe-outputs](/gh-aw/reference/safe-outputs/)), explicit `network` config, no wildcard domains, pinned Actions, no deprecated fields. See [Strict Mode reference](/gh-aw/reference/frontmatter/#strict-mode-strict).
**Strict Mode (`--strict`):** Enforces security best practices: no write permissions (use [safe-outputs](/gh-aw/reference/safe-outputs/)), explicit `network` config, no wildcard domains, pinned actions, no deprecated fields. See [Strict Mode reference](/gh-aw/reference/frontmatter/#strict-mode-strict).

**Shared Workflows:** Workflows without an `on` field are detected as shared components. Validated with relaxed schema and skip compilation. See [Imports reference](/gh-aw/reference/imports/).

Expand Down Expand Up @@ -392,7 +392,7 @@ gh aw trial ./workflow.md --host-repo owner/repo # Run directly in repository
gh aw trial ./workflow.md --dry-run # Preview without executing
```

**Options:** `-e/--engine`, `--repeat`, `--delete-host-repo-after`, `--logical-repo/-l`, `--clone-repo`, `--trigger-context`, `--host-repo`, `--dry-run`, `--append`, `--auto-merge-prs`, `--no-security-scanner`, `--force-delete-host-repo-before`, `--json/-j`, `--timeout`, `--yes/-y`
**Options:** `-e/--engine`, `--repeat`, `--delete-host-repo-after`, `--logical-repo/-l`, `--clone-repo`, `--trigger-context`, `--host-repo`, `--dry-run`, `--append`, `--auto-merge-prs`, `--no-security-scanner`, `--delete-host-repo-before`, `--json/-j`, `--timeout`, `--yes/-y`

**Secret Handling:** API keys required for the selected engine are automatically checked. If missing from the target repository, they are prompted for interactively and uploaded.

Expand All @@ -410,7 +410,7 @@ gh aw run workflow --dry-run # Preview without triggering workflo
gh aw run workflow --json # Output triggered workflow results as JSON
```

**Options:** `--repeat`, `--push` (see [--push flag](#the---push-flag)), `--ref`, `--enable-if-needed`, `--json/-j`, `--auto-merge-prs`, `--dry-run`, `--engine/-e`, `--raw-field/-F`, `--repo/-r`, `--approve`
**Options:** `--repeat`, `--push` (see [--push flag](#the---push-flag)), `--ref`, `--enable-if-needed`, `--json/-j`, `--auto-merge-prs`, `--dry-run`, `--engine/-e`, `--raw-field`, `--repo/-r`, `--approve`

When `--json` is set, a JSON array of triggered workflow results is written to stdout.

Expand Down Expand Up @@ -698,12 +698,12 @@ gh aw disable ci-doctor --repo owner/repo # Disable in specific repository
Remove workflows (both `.md` and `.lock.yml`). Accepts a workflow ID (basename without `.md`) or a substring pattern matching multiple workflows. By default, also removes orphaned include files no longer referenced by any workflow.

```bash wrap
gh aw remove my-workflow # Remove specific workflow
gh aw remove test- # Remove all workflows containing 'test-' in their name
gh aw remove my-workflow --keep-orphans # Remove but keep orphaned include files
gh aw remove my-workflow # Remove specific workflow
gh aw remove test- # Remove all workflows containing 'test-' in their name
gh aw remove my-workflow --no-remove-orphans # Remove but keep orphaned include files
```

**Options:** `--dir/-d`, `--keep-orphans`
**Options:** `--dir/-d`, `--no-remove-orphans`

#### `update`

Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func registerAuditCommandFlags(cmd *cobra.Command) {
cmd.Flags().Bool("stdin", false, "Read workflow run IDs or URLs from stdin (one per line) instead of positional arguments")
cmd.Flags().String("experiment", "", "Filter to runs that include this experiment name")
cmd.Flags().String("variant", "", "Filter to runs with a specific variant value (requires --experiment)")
cmd.Flags().Bool("evals", false, "Skip runs that do not contain evals results (evals.jsonl); automatically downloads the usage artifact (which includes evals) when --artifacts is narrowed")
cmd.Flags().Bool("evals", false, "Filter to runs containing evals results (evals.jsonl); automatically downloads the usage artifact (which includes evals) when --artifacts is narrowed")
RegisterDirFlagCompletion(cmd, "output")
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/checks_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const (
CheckStatePending CheckState = "pending"
// CheckStateNoChecks indicates no checks have been configured or triggered.
CheckStateNoChecks CheckState = "no_checks"
// CheckStatePolicyBlocked indicates policy or account gates are blocking the PR.
// CheckStatePolicyBlocked indicates policy or account gates blocked the PR.
CheckStatePolicyBlocked CheckState = "policy_blocked"
// CheckStateSuccess indicates all checks passed.
CheckStateSuccess CheckState = "success"
Expand Down Expand Up @@ -85,7 +85,7 @@ Maps PR check rollups to one of the following normalized states:
failed - one or more checks failed
pending - checks are still running or queued
no_checks - no checks configured or triggered
policy_blocked - policy or account gates are blocking the PR
policy_blocked - policy or account gates blocked the PR

` + "Raw check run and commit status signals are included in JSON output." + `

Expand Down
28 changes: 27 additions & 1 deletion pkg/cli/compile_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ var (
binaryTempDir string
)

const (
windowsRemoveRetries = 10
windowsRemoveRetryDelay = 100 * time.Millisecond
)

// TestMain builds the gh-aw binary once before running tests
func TestMain(m *testing.M) {
// Get project root
Expand Down Expand Up @@ -150,7 +155,7 @@ func setupIntegrationTest(t *testing.T) *integrationTestSetup {
if err != nil {
t.Fatalf("Failed to change back to original working directory: %v", err)
}
err = os.RemoveAll(tempDir)
err = removeAllWithRetry(tempDir)
if err != nil {
t.Fatalf("Failed to remove temp directory: %v", err)
}
Expand Down Expand Up @@ -229,6 +234,27 @@ Please check the repository for any open issues and create a summary.
t.Logf("Successfully compiled workflow to %s", lockFilePath)
}

func removeAllWithRetry(path string) error {
attempts := 1
if runtime.GOOS == "windows" {
attempts = windowsRemoveRetries
}

var err error
for i := 0; i < attempts; i++ {
err = os.RemoveAll(path)
if err == nil || os.IsNotExist(err) {
return nil
}
if runtime.GOOS != "windows" {
return err
}
time.Sleep(windowsRemoveRetryDelay)
}

return err
}

func TestCompileWithIncludeWithEmptyFrontmatterUnderPty(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("PTY-based test is not supported on Windows")
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/doctor_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ target the correct host.`,
},
}

cmd.Flags().StringP("repo", "r", "", "Target repository in owner/repo format")
cmd.Flags().StringP("repo", "r", "", "Target repository in [HOST/]owner/repo format")
cmd.Flags().StringP("dir", "d", "", "Checkout directory to inspect (defaults to the repo name)")
cmd.Flags().String("require-owner-type", "any", "Require a specific owner type: any, org, or user")
addJSONFlag(cmd)
Expand Down
18 changes: 0 additions & 18 deletions pkg/cli/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,6 @@ func TestShortFlagConsistency(t *testing.T) {
description: "compile should have force short flag",
},

// -F flag (raw-field in run command)
{
name: "run command has -F for --raw-field",
shortFlag: "F",
longFlag: "raw-field",
commandSetup: func() *cobra.Command { return createRunCommandStub() },
shouldExist: true,
description: "run should have raw-field short flag (uppercase F)",
},

// -j flag (json)
{
name: "compile command has -j for --json",
Expand Down Expand Up @@ -272,14 +262,6 @@ func createNewCommandStub() *cobra.Command {
return cmd
}

func createRunCommandStub() *cobra.Command {
cmd := &cobra.Command{Use: "run"}
cmd.Flags().StringArrayP("raw-field", "F", []string{}, "Add string parameter")
cmd.Flags().StringP("engine", "e", "", "Override AI engine")
cmd.Flags().StringP("repo", "r", "", "Target repository")
return cmd
}

func createEnableCommandStub() *cobra.Command {
cmd := &cobra.Command{Use: "enable"}
cmd.Flags().StringP("repo", "r", "", "Target repository")
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/mcp_tools_readonly.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ Maps PR check rollups to one of the following normalized states:
failed - one or more checks failed
pending - checks are still running or queued
no_checks - no checks configured or triggered
policy_blocked - policy or account gates are blocking the PR
policy_blocked - policy or account gates blocked the PR

Returns JSON with two state fields:
state - aggregate state across all check runs and commit statuses
Expand Down
Loading
Loading