Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Add draft boolean option to output.pull-request feature
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
  • Loading branch information
Copilot and pelikhan committed Aug 21, 2025
commit 05f298496871bc01c762b5a8fe15a33f31eec221
9 changes: 8 additions & 1 deletion .github/workflows/test-claude.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion docs/frontmatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ output:
pull-request:
title-prefix: "[ai] " # Optional: prefix for PR titles
labels: [automation, ai-agent] # Optional: labels to attach to PRs
draft: true # Optional: create as draft PR (defaults to true)
```

### Issue Creation (`output.issue`)
Expand Down Expand Up @@ -353,7 +354,7 @@ This automatically creates GitHub issues or comments from the agent's analysis w
- **Dependencies**: Runs after the main agent job (`needs: [main-job-name]`)
- **Permissions**: Only the pull request creation job has `contents: write` and `pull-requests: write` permissions
- **Timeout**: 10-minute timeout to prevent hanging
- **Environment Variables**: Configuration passed via `GITHUB_AW_PR_TITLE_PREFIX`, `GITHUB_AW_PR_LABELS`, `GITHUB_AW_WORKFLOW_ID`, and `GITHUB_AW_BASE_BRANCH`
- **Environment Variables**: Configuration passed via `GITHUB_AW_PR_TITLE_PREFIX`, `GITHUB_AW_PR_LABELS`, `GITHUB_AW_PR_DRAFT`, `GITHUB_AW_WORKFLOW_ID`, and `GITHUB_AW_BASE_BRANCH`
- **Branch Creation**: Uses cryptographic random hex for secure branch naming (`{workflowId}/{randomHex}`)
- **Git Operations**: Creates branch using git CLI, applies patches, commits changes, and pushes to GitHub
- **Outputs**: Returns `pr_number` and `pr_url` for downstream jobs
Expand All @@ -364,6 +365,7 @@ output:
pull-request:
title-prefix: "[ai] " # Optional: prefix for PR titles
labels: [automation, ai-agent] # Optional: labels to attach to PRs
draft: true # Optional: create as draft PR (defaults to true)
```

**Example workflow using pull request creation:**
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions pkg/cli/templates/instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ The YAML frontmatter supports these fields:
pull-request:
title-prefix: "[ai] " # Optional: prefix for PR titles
labels: [automation, ai-agent] # Optional: labels to attach to PRs
draft: true # Optional: create as draft PR (defaults to true)
```
**Important**: When using `output.pull-request`, the main job does **not** need `contents: write` or `pull-requests: write` permissions since PR creation is handled by a separate job with appropriate permissions. The agent must create git patches in `/tmp/aw.patch`.

Expand Down Expand Up @@ -392,6 +393,7 @@ output:
# pull-request:
# title-prefix: "[ai] "
# labels: [automation]
# draft: false # Create non-draft PR
# OR for comments:
# comment: {}
```
Expand Down Expand Up @@ -450,6 +452,7 @@ output:
pull-request:
title-prefix: "[bot] "
labels: [automation, ai-generated]
draft: false # Create non-draft PR for immediate review
---

# Code Improvement Agent
Expand Down
4 changes: 4 additions & 0 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,10 @@
"items": {
"type": "string"
}
},
"draft": {
"type": "boolean",
"description": "Whether to create pull request as draft (defaults to true)"
}
},
"additionalProperties": false
Expand Down
14 changes: 14 additions & 0 deletions pkg/workflow/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ type CommentConfig struct {
type PullRequestConfig struct {
TitlePrefix string `yaml:"title-prefix,omitempty"`
Labels []string `yaml:"labels,omitempty"`
Draft *bool `yaml:"draft,omitempty"` // Pointer to distinguish between unset (nil) and explicitly false
}

// CompileWorkflow converts a markdown workflow to GitHub Actions YAML
Expand Down Expand Up @@ -1769,6 +1770,12 @@ func (c *Compiler) buildCreateOutputPullRequestJob(data *WorkflowData, mainJobNa
labelsStr := strings.Join(data.Output.PullRequest.Labels, ",")
steps = append(steps, fmt.Sprintf(" GITHUB_AW_PR_LABELS: %q\n", labelsStr))
}
// Pass draft setting - default to true for backwards compatibility
draftValue := true // Default value
if data.Output.PullRequest.Draft != nil {
draftValue = *data.Output.PullRequest.Draft
}
steps = append(steps, fmt.Sprintf(" GITHUB_AW_PR_DRAFT: %q\n", fmt.Sprintf("%t", draftValue)))

steps = append(steps, " with:\n")
steps = append(steps, " script: |\n")
Expand Down Expand Up @@ -2244,6 +2251,13 @@ func (c *Compiler) extractOutputConfig(frontmatter map[string]any) *OutputConfig
}
}

// Parse draft
if draft, exists := pullRequestMap["draft"]; exists {
if draftBool, ok := draft.(bool); ok {
pullRequestConfig.Draft = &draftBool
}
}

config.PullRequest = pullRequestConfig
}
}
Expand Down
8 changes: 7 additions & 1 deletion pkg/workflow/js/create_pull_request.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,13 @@ async function main() {
const labelsEnv = process.env.GITHUB_AW_PR_LABELS;
const labels = labelsEnv ? labelsEnv.split(',').map(label => label.trim()).filter(label => label) : [];

// Parse draft setting from environment variable (defaults to true)
const draftEnv = process.env.GITHUB_AW_PR_DRAFT;
const draft = draftEnv ? draftEnv.toLowerCase() === 'true' : true;

console.log('Creating pull request with title:', title);
console.log('Labels:', labels);
console.log('Draft:', draft);
console.log('Body length:', body.length);

// Generate unique branch name using cryptographic random hex
Expand Down Expand Up @@ -124,7 +129,8 @@ async function main() {
title: title,
body: body,
head: branchName,
base: baseBranch
base: baseBranch,
draft: draft
});

console.log('Created pull request #' + pullRequest.number + ': ' + pullRequest.html_url);
Expand Down
155 changes: 155 additions & 0 deletions pkg/workflow/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,10 +564,165 @@ This workflow tests the create_pull_request job generation.
t.Error("Expected automation label to be set as environment variable")
}

// Verify draft setting defaults to true
if !strings.Contains(lockContentStr, "GITHUB_AW_PR_DRAFT: \"true\"") {
t.Error("Expected draft to default to true when not specified")
}

// Verify job dependencies
if !strings.Contains(lockContentStr, "needs: test-output-pull-request-job-generation") {
t.Error("Expected create_pull_request job to depend on main job")
}

t.Logf("Generated workflow content:\n%s", lockContentStr)
}

func TestOutputPullRequestDraftFalse(t *testing.T) {
// Create temporary directory for test files
tmpDir, err := os.MkdirTemp("", "output-pr-draft-false-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

// Test case with output.pull-request configuration with draft: false
testContent := `---
on: push
permissions:
contents: read
pull-requests: write
tools:
github:
allowed: [list_issues]
engine: claude
output:
pull-request:
title-prefix: "[agent] "
labels: [automation]
draft: false
---

# Test Output Pull Request with Draft False

This workflow tests the create_pull_request job generation with draft: false.
`

testFile := filepath.Join(tmpDir, "test-output-pr-draft-false.md")
if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil {
t.Fatal(err)
}

compiler := NewCompiler(false, "", "test")

// Compile the workflow
err = compiler.CompileWorkflow(testFile)
if err != nil {
t.Fatalf("Unexpected error compiling workflow with output pull-request draft: false: %v", err)
}

// Read the generated lock file
lockFile := strings.TrimSuffix(testFile, ".md") + ".lock.yml"
lockContent, err := os.ReadFile(lockFile)
if err != nil {
t.Fatalf("Failed to read generated lock file: %v", err)
}

// Convert to string for easier testing
lockContentStr := string(lockContent)

// Verify create_pull_request job is present
if !strings.Contains(lockContentStr, "create_pull_request:") {
t.Error("Expected 'create_pull_request' job to be in generated workflow")
}

// Verify draft setting is false
if !strings.Contains(lockContentStr, "GITHUB_AW_PR_DRAFT: \"false\"") {
t.Error("Expected draft to be set to false when explicitly specified")
}

// Verify other expected environment variables are still present
if !strings.Contains(lockContentStr, "GITHUB_AW_PR_TITLE_PREFIX: \"[agent] \"") {
t.Error("Expected title prefix to be set as environment variable")
}

if !strings.Contains(lockContentStr, "GITHUB_AW_PR_LABELS: \"automation\"") {
t.Error("Expected automation label to be set as environment variable")
}

t.Logf("Generated workflow content:\n%s", lockContentStr)
}

func TestOutputPullRequestDraftTrue(t *testing.T) {
// Create temporary directory for test files
tmpDir, err := os.MkdirTemp("", "output-pr-draft-true-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

// Test case with output.pull-request configuration with draft: true
testContent := `---
on: push
permissions:
contents: read
pull-requests: write
tools:
github:
allowed: [list_issues]
engine: claude
output:
pull-request:
title-prefix: "[agent] "
labels: [automation]
draft: true
---

# Test Output Pull Request with Draft True

This workflow tests the create_pull_request job generation with draft: true.
`

testFile := filepath.Join(tmpDir, "test-output-pr-draft-true.md")
if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil {
t.Fatal(err)
}

compiler := NewCompiler(false, "", "test")

// Compile the workflow
err = compiler.CompileWorkflow(testFile)
if err != nil {
t.Fatalf("Unexpected error compiling workflow with output pull-request draft: true: %v", err)
}

// Read the generated lock file
lockFile := strings.TrimSuffix(testFile, ".md") + ".lock.yml"
lockContent, err := os.ReadFile(lockFile)
if err != nil {
t.Fatalf("Failed to read generated lock file: %v", err)
}

// Convert to string for easier testing
lockContentStr := string(lockContent)

// Verify create_pull_request job is present
if !strings.Contains(lockContentStr, "create_pull_request:") {
t.Error("Expected 'create_pull_request' job to be in generated workflow")
}

// Verify draft setting is true
if !strings.Contains(lockContentStr, "GITHUB_AW_PR_DRAFT: \"true\"") {
t.Error("Expected draft to be set to true when explicitly specified")
}

// Verify other expected environment variables are still present
if !strings.Contains(lockContentStr, "GITHUB_AW_PR_TITLE_PREFIX: \"[agent] \"") {
t.Error("Expected title prefix to be set as environment variable")
}

if !strings.Contains(lockContentStr, "GITHUB_AW_PR_LABELS: \"automation\"") {
t.Error("Expected automation label to be set as environment variable")
}

t.Logf("Generated workflow content:\n%s", lockContentStr)
}