diff --git a/pkg/workflow/cache_memory_prompt.go b/pkg/workflow/cache_memory_prompt.go deleted file mode 100644 index cb9be4b69ed..00000000000 --- a/pkg/workflow/cache_memory_prompt.go +++ /dev/null @@ -1,19 +0,0 @@ -package workflow - -import ( - "strings" -) - -// generateCacheMemoryPromptStep generates a separate step for cache memory instructions -// when cache-memory is enabled, informing the agent about persistent storage capabilities -func (c *Compiler) generateCacheMemoryPromptStep(yaml *strings.Builder, config *CacheMemoryConfig) { - if config == nil || len(config.Caches) == 0 { - return - } - - appendPromptStepWithHeredoc(yaml, - "Append cache memory instructions to prompt", - func(y *strings.Builder) { - generateCacheMemoryPromptSection(y, config) - }) -} diff --git a/pkg/workflow/cache_memory_prompt_test.go b/pkg/workflow/cache_memory_prompt_test.go deleted file mode 100644 index 8a49e979c36..00000000000 --- a/pkg/workflow/cache_memory_prompt_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package workflow - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -func TestCacheMemoryPromptIncludedWhenEnabled(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-cache-memory-prompt-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow with cache-memory enabled - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: push -engine: claude -tools: - cache-memory: true ---- - -# Test Workflow with Cache Memory - -This is a test workflow with cache-memory enabled. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Test 1: Verify cache memory prompt step is created - if !strings.Contains(lockStr, "- name: Append cache memory instructions to prompt") { - t.Error("Expected 'Append cache memory instructions to prompt' step in generated workflow") - } - - // Test 2: Verify the instruction text contains cache folder information - if !strings.Contains(lockStr, "Cache Folder Available") { - t.Error("Expected 'Cache Folder Available' header in generated workflow") - } - - // Test 3: Verify the instruction text contains the cache directory path - if !strings.Contains(lockStr, "/tmp/gh-aw/cache-memory/") { - t.Error("Expected '/tmp/gh-aw/cache-memory/' reference in generated workflow") - } - - // Test 4: Verify the instruction mentions persistent cache - if !strings.Contains(lockStr, "persist") { - t.Error("Expected 'persist' reference in generated workflow") - } - - t.Logf("Successfully verified cache memory instructions are included in generated workflow") -} - -func TestCacheMemoryPromptNotIncludedWhenDisabled(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-no-cache-memory-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow WITHOUT cache-memory - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: push -engine: claude -tools: - github: ---- - -# Test Workflow without Cache Memory - -This is a test workflow without cache-memory. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Test: Verify cache memory prompt step is NOT created - if strings.Contains(lockStr, "- name: Append cache memory instructions to prompt") { - t.Error("Did not expect 'Append cache memory instructions to prompt' step in workflow without cache-memory") - } - - if strings.Contains(lockStr, "Cache Folder Available") { - t.Error("Did not expect 'Cache Folder Available' header in workflow without cache-memory") - } - - t.Logf("Successfully verified cache memory instructions are NOT included when cache-memory is disabled") -} - -func TestCacheMemoryPromptMultipleCaches(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-multi-cache-memory-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow with multiple cache-memory entries - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: push -engine: claude -tools: - cache-memory: - - id: default - key: cache-1 - - id: session - key: cache-2 ---- - -# Test Workflow with Multiple Caches - -This is a test workflow with multiple cache-memory entries. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Test 1: Verify cache memory prompt step is created - if !strings.Contains(lockStr, "- name: Append cache memory instructions to prompt") { - t.Error("Expected 'Append cache memory instructions to prompt' step in generated workflow") - } - - // Test 2: Verify plural form is used for multiple caches - if !strings.Contains(lockStr, "Cache Folders Available") { - t.Error("Expected 'Cache Folders Available' (plural) header for multiple caches") - } - - // Test 3: Verify both cache directories are mentioned - if !strings.Contains(lockStr, "/tmp/gh-aw/cache-memory/") { - t.Error("Expected '/tmp/gh-aw/cache-memory/' reference for default cache") - } - - if !strings.Contains(lockStr, "/tmp/gh-aw/cache-memory-session/") { - t.Error("Expected '/tmp/gh-aw/cache-memory-session/' reference for session cache") - } - - t.Logf("Successfully verified cache memory instructions handle multiple caches") -} diff --git a/pkg/workflow/edit_tool_prompt.go b/pkg/workflow/edit_tool_prompt.go deleted file mode 100644 index 2e5d26d340b..00000000000 --- a/pkg/workflow/edit_tool_prompt.go +++ /dev/null @@ -1,22 +0,0 @@ -package workflow - -import ( - "strings" -) - -// hasEditTool checks if the edit tool is enabled in the tools configuration -func hasEditTool(parsedTools *Tools) bool { - if parsedTools == nil { - return false - } - return parsedTools.Edit != nil -} - -// generateEditToolPromptStep generates a separate step for edit tool accessibility instructions -// Only generates the step if edit tool is enabled in the workflow -func (c *Compiler) generateEditToolPromptStep(yaml *strings.Builder, data *WorkflowData) { - generateStaticPromptStep(yaml, - "Append edit tool accessibility instructions to prompt", - editToolPromptText, - hasEditTool(data.ParsedTools)) -} diff --git a/pkg/workflow/edit_tool_prompt_test.go b/pkg/workflow/edit_tool_prompt_test.go deleted file mode 100644 index fb775769555..00000000000 --- a/pkg/workflow/edit_tool_prompt_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package workflow - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -func TestEditToolPromptIncludedWhenEnabled(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-edit-prompt-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow with edit tool enabled - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: push -engine: claude -tools: - edit: ---- - -# Test Workflow with Edit Tool - -This is a test workflow with edit tool enabled. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Test 1: Verify edit tool prompt step is created - if !strings.Contains(lockStr, "- name: Append edit tool accessibility instructions to prompt") { - t.Error("Expected 'Append edit tool accessibility instructions to prompt' step in generated workflow") - } - - // Test 2: Verify the instruction text contains the workspace path - if !strings.Contains(lockStr, "$GITHUB_WORKSPACE") { - t.Error("Expected $GITHUB_WORKSPACE reference in generated workflow") - } - - // Test 3: Verify the instruction text contains the /tmp/gh-aw/ path - if !strings.Contains(lockStr, "/tmp/gh-aw/") { - t.Error("Expected /tmp/gh-aw/ reference in generated workflow") - } - - // Test 4: Verify the instruction mentions file-editing section - if !strings.Contains(lockStr, "") { - t.Error("Expected '' XML tag in generated workflow") - } - - // Test 5: Verify the instruction mentions allowed paths - if !strings.Contains(lockStr, "") { - t.Error("Expected '' XML tag in generated workflow") - } - - t.Logf("Successfully verified edit tool accessibility instructions are included in generated workflow") -} - -func TestEditToolPromptNotIncludedWhenDisabled(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-no-edit-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow WITHOUT edit tool - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: push -engine: codex -tools: - github: ---- - -# Test Workflow without Edit Tool - -This is a test workflow without edit tool. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Test: Verify edit tool prompt step is NOT created - if strings.Contains(lockStr, "- name: Append edit tool accessibility instructions to prompt") { - t.Error("Did not expect 'Append edit tool accessibility instructions to prompt' step in workflow without edit tool") - } - - if strings.Contains(lockStr, "File Editing Access") { - t.Error("Did not expect 'File Editing Access' header in workflow without edit tool") - } - - t.Logf("Successfully verified edit tool accessibility instructions are NOT included when edit tool is disabled") -} - -func TestEditToolPromptOrderAfterPlaywright(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-edit-order-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow with both playwright and edit tools - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: push -engine: claude -tools: - playwright: - edit: ---- - -# Test Workflow - -This is a test workflow to verify edit instructions come after playwright. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Find positions of playwright and edit instructions - playwrightPos := strings.Index(lockStr, "Append playwright output directory instructions to prompt") - editPos := strings.Index(lockStr, "Append edit tool accessibility instructions to prompt") - - // Test: Verify edit instructions come after playwright instructions - if playwrightPos == -1 { - t.Error("Expected playwright output directory instructions in generated workflow") - } - - if editPos == -1 { - t.Error("Expected edit tool accessibility instructions in generated workflow") - } - - if playwrightPos != -1 && editPos != -1 && editPos <= playwrightPos { - t.Errorf("Expected edit instructions to come after playwright instructions, but found at positions Playwright=%d, Edit=%d", playwrightPos, editPos) - } - - t.Logf("Successfully verified edit instructions come after playwright instructions in generated workflow") -} diff --git a/pkg/workflow/playwright_prompt.go b/pkg/workflow/playwright_prompt.go deleted file mode 100644 index be9298fd6cb..00000000000 --- a/pkg/workflow/playwright_prompt.go +++ /dev/null @@ -1,22 +0,0 @@ -package workflow - -import ( - "strings" -) - -// hasPlaywrightTool checks if the playwright tool is enabled in the tools configuration -func hasPlaywrightTool(parsedTools *Tools) bool { - if parsedTools == nil { - return false - } - return parsedTools.Playwright != nil -} - -// generatePlaywrightPromptStep generates a separate step for playwright output directory instructions -// Only generates the step if playwright tool is enabled in the workflow -func (c *Compiler) generatePlaywrightPromptStep(yaml *strings.Builder, data *WorkflowData) { - generateStaticPromptStep(yaml, - "Append playwright output directory instructions to prompt", - playwrightPromptText, - hasPlaywrightTool(data.ParsedTools)) -} diff --git a/pkg/workflow/playwright_prompt_test.go b/pkg/workflow/playwright_prompt_test.go deleted file mode 100644 index c7c4fbc7436..00000000000 --- a/pkg/workflow/playwright_prompt_test.go +++ /dev/null @@ -1,181 +0,0 @@ -package workflow - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -func TestPlaywrightPromptIncludedWhenEnabled(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-playwright-prompt-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow with playwright tool enabled - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: push -engine: claude -tools: - playwright: ---- - -# Test Workflow with Playwright - -This is a test workflow with playwright enabled. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Test 1: Verify playwright prompt step is created - if !strings.Contains(lockStr, "- name: Append playwright output directory instructions to prompt") { - t.Error("Expected 'Append playwright output directory instructions to prompt' step in generated workflow") - } - - // Test 2: Verify the instruction text contains the output directory path - if !strings.Contains(lockStr, "/tmp/gh-aw/mcp-logs/playwright/") { - t.Error("Expected playwright output directory path /tmp/gh-aw/mcp-logs/playwright/ in generated workflow") - } - - // Test 3: Verify the instruction contains playwright-output XML tag - if !strings.Contains(lockStr, "") { - t.Error("Expected '' XML tag in generated workflow") - } - - t.Logf("Successfully verified playwright output directory instructions are included in generated workflow") -} - -func TestPlaywrightPromptNotIncludedWhenDisabled(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-no-playwright-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow WITHOUT playwright tool - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: push -engine: codex -tools: - github: ---- - -# Test Workflow without Playwright - -This is a test workflow without playwright. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Test: Verify playwright prompt step is NOT created - if strings.Contains(lockStr, "- name: Append playwright output directory instructions to prompt") { - t.Error("Did not expect 'Append playwright output directory instructions to prompt' step in workflow without playwright") - } - - if strings.Contains(lockStr, "Playwright Output Directory") { - t.Error("Did not expect 'Playwright Output Directory' header in workflow without playwright") - } - - t.Logf("Successfully verified playwright output directory instructions are NOT included when playwright is disabled") -} - -func TestPlaywrightPromptOrderAfterTempFolder(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-playwright-order-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow with playwright - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: push -engine: claude -tools: - playwright: ---- - -# Test Workflow - -This is a test workflow to verify playwright instructions come after temp folder. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Find positions of temp folder and playwright instructions - tempFolderPos := strings.Index(lockStr, "Append temporary folder instructions to prompt") - playwrightPos := strings.Index(lockStr, "Append playwright output directory instructions to prompt") - - // Test: Verify playwright instructions come after temp folder instructions - if tempFolderPos == -1 { - t.Error("Expected temporary folder instructions in generated workflow") - } - - if playwrightPos == -1 { - t.Error("Expected playwright output directory instructions in generated workflow") - } - - if tempFolderPos != -1 && playwrightPos != -1 && playwrightPos <= tempFolderPos { - t.Errorf("Expected playwright instructions to come after temp folder instructions, but found at positions TempFolder=%d, Playwright=%d", tempFolderPos, playwrightPos) - } - - t.Logf("Successfully verified playwright instructions come after temp folder instructions in generated workflow") -} diff --git a/pkg/workflow/pr_prompt.go b/pkg/workflow/pr_prompt.go deleted file mode 100644 index 53adcc3566b..00000000000 --- a/pkg/workflow/pr_prompt.go +++ /dev/null @@ -1,61 +0,0 @@ -package workflow - -import ( - "strings" -) - -// generatePRContextPromptStep generates a separate step for PR context instructions -func (c *Compiler) generatePRContextPromptStep(yaml *strings.Builder, data *WorkflowData) { - // Check if any of the workflow's event triggers are comment-related events - hasCommentTriggers := c.hasCommentRelatedTriggers(data) - - if !hasCommentTriggers { - return // No comment-related triggers, skip PR context instructions - } - - // Also check if checkout step will be added - only show prompt if checkout happens - needsCheckout := c.shouldAddCheckoutStep(data) - if !needsCheckout { - return // No checkout, so no PR branch checkout will happen - } - - // Check that permissions allow contents read access - permParser := NewPermissionsParser(data.Permissions) - if !permParser.HasContentsReadAccess() { - return // No contents read access, cannot checkout - } - - // Build the condition string - condition := BuildPRCommentCondition() - - // Use shared helper but we need to render condition manually since it requires RenderConditionAsIf - // which is more complex than a simple if: string - yaml.WriteString(" - name: Append PR context instructions to prompt\n") - RenderConditionAsIf(yaml, condition, " ") - yaml.WriteString(" env:\n") - yaml.WriteString(" GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt\n") - yaml.WriteString(" run: |\n") - WritePromptTextToYAML(yaml, prContextPromptText, " ") -} - -// hasCommentRelatedTriggers checks if the workflow has any comment-related event triggers -func (c *Compiler) hasCommentRelatedTriggers(data *WorkflowData) bool { - // Check for command trigger (which expands to comment events) - if data.Command != "" { - return true - } - - if data.On == "" { - return false - } - - // Check for comment-related event types in the "on" configuration - commentEvents := []string{"issue_comment", "pull_request_review_comment", "pull_request_review"} - for _, event := range commentEvents { - if strings.Contains(data.On, event) { - return true - } - } - - return false -} diff --git a/pkg/workflow/pr_prompt_test.go b/pkg/workflow/pr_prompt_test.go deleted file mode 100644 index 34512286b86..00000000000 --- a/pkg/workflow/pr_prompt_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package workflow - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -func TestPRContextPromptIncludedForIssueComment(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-pr-context-prompt-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow with issue_comment trigger - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: - issue_comment: - types: [created] -permissions: - contents: read -engine: claude ---- - -# Test Workflow with Issue Comment - -This is a test workflow with issue_comment trigger. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Test 1: Verify PR context prompt step is created - if !strings.Contains(lockStr, "- name: Append PR context instructions to prompt") { - t.Error("Expected 'Append PR context instructions to prompt' step in generated workflow") - } - - // Test 2: Verify the instruction mentions PR branch checkout - if !strings.Contains(lockStr, "pull request") { - t.Error("Expected 'pull request' reference in generated workflow") - } - - t.Logf("Successfully verified PR context instructions are included for issue_comment trigger") -} - -func TestPRContextPromptIncludedForCommand(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-pr-context-command-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow with command trigger - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: - command: - name: mybot -permissions: - contents: read -engine: claude ---- - -# Test Workflow with Command - -This is a test workflow with command trigger. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Test: Verify PR context prompt step is created for command triggers - if !strings.Contains(lockStr, "- name: Append PR context instructions to prompt") { - t.Error("Expected 'Append PR context instructions to prompt' step in workflow with command trigger") - } - - t.Logf("Successfully verified PR context instructions are included for command trigger") -} - -func TestPRContextPromptNotIncludedForPush(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-no-pr-context-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow with push trigger (no comment triggers) - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: push -permissions: - contents: read -engine: claude ---- - -# Test Workflow without Comment Triggers - -This is a test workflow with push trigger only. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Test: Verify PR context prompt step is NOT created for push triggers - if strings.Contains(lockStr, "- name: Append PR context instructions to prompt") { - t.Error("Did not expect 'Append PR context instructions to prompt' step for push trigger") - } - - t.Logf("Successfully verified PR context instructions are NOT included for push trigger") -} - -func TestPRContextPromptNotIncludedWithoutCheckout(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "gh-aw-pr-no-checkout-test-*") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Create a test workflow with comment trigger but no checkout (no contents permission) - testFile := filepath.Join(tmpDir, "test-workflow.md") - testContent := `--- -on: - issue_comment: - types: [created] -permissions: - issues: read -engine: claude ---- - -# Test Workflow without Contents Permission - -This is a test workflow without contents read permission. -` - - if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { - t.Fatalf("Failed to create test workflow: %v", err) - } - - // Compile the workflow - compiler := NewCompiler(false, "", "test") - if err := compiler.CompileWorkflow(testFile); err != nil { - t.Fatalf("Failed to compile workflow: %v", err) - } - - // Read the generated lock file - lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) - lockContent, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read generated lock file: %v", err) - } - - lockStr := string(lockContent) - - // Test: Verify PR context prompt step is NOT created without contents permission - if strings.Contains(lockStr, "- name: Append PR context instructions to prompt") { - t.Error("Did not expect 'Append PR context instructions to prompt' step without contents read permission") - } - - t.Logf("Successfully verified PR context instructions are NOT included without contents permission") -} diff --git a/pkg/workflow/prompts.go b/pkg/workflow/prompts.go new file mode 100644 index 00000000000..967f41828cf --- /dev/null +++ b/pkg/workflow/prompts.go @@ -0,0 +1,148 @@ +package workflow + +import ( + "strings" +) + +// prompts.go consolidates all prompt-related functions for agentic workflows. +// This file contains functions that generate workflow steps to append various +// contextual instructions to the agent's prompt file during execution. +// +// Prompts are organized by feature area: +// - Safe outputs: Instructions for using the safeoutputs MCP server +// - Cache memory: Instructions for persistent cache folder access +// - Tool prompts: Instructions for specific tools (edit, playwright) +// - PR context: Instructions for pull request branch context + +// ============================================================================ +// Safe Outputs Prompts +// ============================================================================ + +// generateSafeOutputsPromptStep generates a separate step for safe outputs instructions +// This tells agents to use the safeoutputs MCP server instead of gh CLI +func (c *Compiler) generateSafeOutputsPromptStep(yaml *strings.Builder, hasSafeOutputs bool) { + generateStaticPromptStep(yaml, + "Append safe outputs instructions to prompt", + safeOutputsPromptText, + hasSafeOutputs) +} + +// ============================================================================ +// Cache Memory Prompts +// ============================================================================ + +// generateCacheMemoryPromptStep generates a separate step for cache memory instructions +// when cache-memory is enabled, informing the agent about persistent storage capabilities +func (c *Compiler) generateCacheMemoryPromptStep(yaml *strings.Builder, config *CacheMemoryConfig) { + if config == nil || len(config.Caches) == 0 { + return + } + + appendPromptStepWithHeredoc(yaml, + "Append cache memory instructions to prompt", + func(y *strings.Builder) { + generateCacheMemoryPromptSection(y, config) + }) +} + +// ============================================================================ +// Tool Prompts - Edit Tool +// ============================================================================ + +// hasEditTool checks if the edit tool is enabled in the tools configuration +func hasEditTool(parsedTools *Tools) bool { + if parsedTools == nil { + return false + } + return parsedTools.Edit != nil +} + +// generateEditToolPromptStep generates a separate step for edit tool accessibility instructions +// Only generates the step if edit tool is enabled in the workflow +func (c *Compiler) generateEditToolPromptStep(yaml *strings.Builder, data *WorkflowData) { + generateStaticPromptStep(yaml, + "Append edit tool accessibility instructions to prompt", + editToolPromptText, + hasEditTool(data.ParsedTools)) +} + +// ============================================================================ +// Tool Prompts - Playwright +// ============================================================================ + +// hasPlaywrightTool checks if the playwright tool is enabled in the tools configuration +func hasPlaywrightTool(parsedTools *Tools) bool { + if parsedTools == nil { + return false + } + return parsedTools.Playwright != nil +} + +// generatePlaywrightPromptStep generates a separate step for playwright output directory instructions +// Only generates the step if playwright tool is enabled in the workflow +func (c *Compiler) generatePlaywrightPromptStep(yaml *strings.Builder, data *WorkflowData) { + generateStaticPromptStep(yaml, + "Append playwright output directory instructions to prompt", + playwrightPromptText, + hasPlaywrightTool(data.ParsedTools)) +} + +// ============================================================================ +// PR Context Prompts +// ============================================================================ + +// generatePRContextPromptStep generates a separate step for PR context instructions +func (c *Compiler) generatePRContextPromptStep(yaml *strings.Builder, data *WorkflowData) { + // Check if any of the workflow's event triggers are comment-related events + hasCommentTriggers := c.hasCommentRelatedTriggers(data) + + if !hasCommentTriggers { + return // No comment-related triggers, skip PR context instructions + } + + // Also check if checkout step will be added - only show prompt if checkout happens + needsCheckout := c.shouldAddCheckoutStep(data) + if !needsCheckout { + return // No checkout, so no PR branch checkout will happen + } + + // Check that permissions allow contents read access + permParser := NewPermissionsParser(data.Permissions) + if !permParser.HasContentsReadAccess() { + return // No contents read access, cannot checkout + } + + // Build the condition string + condition := BuildPRCommentCondition() + + // Use shared helper but we need to render condition manually since it requires RenderConditionAsIf + // which is more complex than a simple if: string + yaml.WriteString(" - name: Append PR context instructions to prompt\n") + RenderConditionAsIf(yaml, condition, " ") + yaml.WriteString(" env:\n") + yaml.WriteString(" GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt\n") + yaml.WriteString(" run: |\n") + WritePromptTextToYAML(yaml, prContextPromptText, " ") +} + +// hasCommentRelatedTriggers checks if the workflow has any comment-related event triggers +func (c *Compiler) hasCommentRelatedTriggers(data *WorkflowData) bool { + // Check for command trigger (which expands to comment events) + if data.Command != "" { + return true + } + + if data.On == "" { + return false + } + + // Check for comment-related event types in the "on" configuration + commentEvents := []string{"issue_comment", "pull_request_review_comment", "pull_request_review"} + for _, event := range commentEvents { + if strings.Contains(data.On, event) { + return true + } + } + + return false +} diff --git a/pkg/workflow/prompts_test.go b/pkg/workflow/prompts_test.go new file mode 100644 index 00000000000..2601d7f8f84 --- /dev/null +++ b/pkg/workflow/prompts_test.go @@ -0,0 +1,821 @@ +package workflow + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +// ============================================================================ +// Safe Outputs Prompt Tests +// ============================================================================ + +func TestGenerateSafeOutputsPromptStep_IncludesWhenEnabled(t *testing.T) { + compiler := &Compiler{} + var yaml strings.Builder + + compiler.generateSafeOutputsPromptStep(&yaml, true) + + output := yaml.String() + if !strings.Contains(output, "Append safe outputs instructions to prompt") { + t.Error("Expected safe outputs prompt step to be generated when enabled") + } + if !strings.Contains(output, "safeoutputs MCP server") { + t.Error("Expected prompt to mention safeoutputs MCP server") + } + if !strings.Contains(output, "gh (GitHub CLI) command is NOT authenticated") { + t.Error("Expected prompt to warn about gh CLI not being authenticated") + } +} + +func TestGenerateSafeOutputsPromptStep_SkippedWhenDisabled(t *testing.T) { + compiler := &Compiler{} + var yaml strings.Builder + + compiler.generateSafeOutputsPromptStep(&yaml, false) + + output := yaml.String() + if strings.Contains(output, "safe outputs") { + t.Error("Expected safe outputs prompt step to NOT be generated when disabled") + } +} + +func TestSafeOutputsPromptText_FollowsXMLFormat(t *testing.T) { + if !strings.Contains(safeOutputsPromptText, "") { + t.Error("Expected prompt to start with XML tag") + } + if !strings.Contains(safeOutputsPromptText, "") { + t.Error("Expected prompt to end with XML tag") + } + if !strings.Contains(safeOutputsPromptText, "") { + t.Error("Expected prompt to contain section") + } + if !strings.Contains(safeOutputsPromptText, "") { + t.Error("Expected prompt to contain section") + } +} + +// ============================================================================ +// Cache Memory Prompt Tests +// ============================================================================ + +func TestCacheMemoryPromptIncludedWhenEnabled(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-cache-memory-prompt-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow with cache-memory enabled + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: push +engine: claude +tools: + cache-memory: true +--- + +# Test Workflow with Cache Memory + +This is a test workflow with cache-memory enabled. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Test 1: Verify cache memory prompt step is created + if !strings.Contains(lockStr, "- name: Append cache memory instructions to prompt") { + t.Error("Expected 'Append cache memory instructions to prompt' step in generated workflow") + } + + // Test 2: Verify the instruction text contains cache folder information + if !strings.Contains(lockStr, "Cache Folder Available") { + t.Error("Expected 'Cache Folder Available' header in generated workflow") + } + + // Test 3: Verify the instruction text contains the cache directory path + if !strings.Contains(lockStr, "/tmp/gh-aw/cache-memory/") { + t.Error("Expected '/tmp/gh-aw/cache-memory/' reference in generated workflow") + } + + // Test 4: Verify the instruction mentions persistent cache + if !strings.Contains(lockStr, "persist") { + t.Error("Expected 'persist' reference in generated workflow") + } + + t.Logf("Successfully verified cache memory instructions are included in generated workflow") +} + +func TestCacheMemoryPromptNotIncludedWhenDisabled(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-no-cache-memory-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow WITHOUT cache-memory + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: push +engine: claude +tools: + github: +--- + +# Test Workflow without Cache Memory + +This is a test workflow without cache-memory. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Test: Verify cache memory prompt step is NOT created + if strings.Contains(lockStr, "- name: Append cache memory instructions to prompt") { + t.Error("Did not expect 'Append cache memory instructions to prompt' step in workflow without cache-memory") + } + + if strings.Contains(lockStr, "Cache Folder Available") { + t.Error("Did not expect 'Cache Folder Available' header in workflow without cache-memory") + } + + t.Logf("Successfully verified cache memory instructions are NOT included when cache-memory is disabled") +} + +func TestCacheMemoryPromptMultipleCaches(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-multi-cache-memory-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow with multiple cache-memory entries + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: push +engine: claude +tools: + cache-memory: + - id: default + key: cache-1 + - id: session + key: cache-2 +--- + +# Test Workflow with Multiple Caches + +This is a test workflow with multiple cache-memory entries. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Test 1: Verify cache memory prompt step is created + if !strings.Contains(lockStr, "- name: Append cache memory instructions to prompt") { + t.Error("Expected 'Append cache memory instructions to prompt' step in generated workflow") + } + + // Test 2: Verify plural form is used for multiple caches + if !strings.Contains(lockStr, "Cache Folders Available") { + t.Error("Expected 'Cache Folders Available' (plural) header for multiple caches") + } + + // Test 3: Verify both cache directories are mentioned + if !strings.Contains(lockStr, "/tmp/gh-aw/cache-memory/") { + t.Error("Expected '/tmp/gh-aw/cache-memory/' reference for default cache") + } + + if !strings.Contains(lockStr, "/tmp/gh-aw/cache-memory-session/") { + t.Error("Expected '/tmp/gh-aw/cache-memory-session/' reference for session cache") + } + + t.Logf("Successfully verified cache memory instructions handle multiple caches") +} + +// ============================================================================ +// Edit Tool Prompt Tests +// ============================================================================ + +func TestEditToolPromptIncludedWhenEnabled(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-edit-prompt-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow with edit tool enabled + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: push +engine: claude +tools: + edit: +--- + +# Test Workflow with Edit Tool + +This is a test workflow with edit tool enabled. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Test 1: Verify edit tool prompt step is created + if !strings.Contains(lockStr, "- name: Append edit tool accessibility instructions to prompt") { + t.Error("Expected 'Append edit tool accessibility instructions to prompt' step in generated workflow") + } + + // Test 2: Verify the instruction text contains the workspace path + if !strings.Contains(lockStr, "$GITHUB_WORKSPACE") { + t.Error("Expected $GITHUB_WORKSPACE reference in generated workflow") + } + + // Test 3: Verify the instruction text contains the /tmp/gh-aw/ path + if !strings.Contains(lockStr, "/tmp/gh-aw/") { + t.Error("Expected /tmp/gh-aw/ reference in generated workflow") + } + + // Test 4: Verify the instruction mentions file-editing section + if !strings.Contains(lockStr, "") { + t.Error("Expected '' XML tag in generated workflow") + } + + // Test 5: Verify the instruction mentions allowed paths + if !strings.Contains(lockStr, "") { + t.Error("Expected '' XML tag in generated workflow") + } + + t.Logf("Successfully verified edit tool accessibility instructions are included in generated workflow") +} + +func TestEditToolPromptNotIncludedWhenDisabled(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-no-edit-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow WITHOUT edit tool + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: push +engine: codex +tools: + github: +--- + +# Test Workflow without Edit Tool + +This is a test workflow without edit tool. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Test: Verify edit tool prompt step is NOT created + if strings.Contains(lockStr, "- name: Append edit tool accessibility instructions to prompt") { + t.Error("Did not expect 'Append edit tool accessibility instructions to prompt' step in workflow without edit tool") + } + + if strings.Contains(lockStr, "File Editing Access") { + t.Error("Did not expect 'File Editing Access' header in workflow without edit tool") + } + + t.Logf("Successfully verified edit tool accessibility instructions are NOT included when edit tool is disabled") +} + +func TestEditToolPromptOrderAfterPlaywright(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-edit-order-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow with both playwright and edit tools + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: push +engine: claude +tools: + playwright: + edit: +--- + +# Test Workflow + +This is a test workflow to verify edit instructions come after playwright. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Find positions of playwright and edit instructions + playwrightPos := strings.Index(lockStr, "Append playwright output directory instructions to prompt") + editPos := strings.Index(lockStr, "Append edit tool accessibility instructions to prompt") + + // Test: Verify edit instructions come after playwright instructions + if playwrightPos == -1 { + t.Error("Expected playwright output directory instructions in generated workflow") + } + + if editPos == -1 { + t.Error("Expected edit tool accessibility instructions in generated workflow") + } + + if playwrightPos != -1 && editPos != -1 && editPos <= playwrightPos { + t.Errorf("Expected edit instructions to come after playwright instructions, but found at positions Playwright=%d, Edit=%d", playwrightPos, editPos) + } + + t.Logf("Successfully verified edit instructions come after playwright instructions in generated workflow") +} + +// ============================================================================ +// Playwright Prompt Tests +// ============================================================================ + +func TestPlaywrightPromptIncludedWhenEnabled(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-playwright-prompt-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow with playwright tool enabled + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: push +engine: claude +tools: + playwright: +--- + +# Test Workflow with Playwright + +This is a test workflow with playwright enabled. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Test 1: Verify playwright prompt step is created + if !strings.Contains(lockStr, "- name: Append playwright output directory instructions to prompt") { + t.Error("Expected 'Append playwright output directory instructions to prompt' step in generated workflow") + } + + // Test 2: Verify the instruction text contains the output directory path + if !strings.Contains(lockStr, "/tmp/gh-aw/mcp-logs/playwright/") { + t.Error("Expected playwright output directory path /tmp/gh-aw/mcp-logs/playwright/ in generated workflow") + } + + // Test 3: Verify the instruction contains playwright-output XML tag + if !strings.Contains(lockStr, "") { + t.Error("Expected '' XML tag in generated workflow") + } + + t.Logf("Successfully verified playwright output directory instructions are included in generated workflow") +} + +func TestPlaywrightPromptNotIncludedWhenDisabled(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-no-playwright-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow WITHOUT playwright tool + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: push +engine: codex +tools: + github: +--- + +# Test Workflow without Playwright + +This is a test workflow without playwright. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Test: Verify playwright prompt step is NOT created + if strings.Contains(lockStr, "- name: Append playwright output directory instructions to prompt") { + t.Error("Did not expect 'Append playwright output directory instructions to prompt' step in workflow without playwright") + } + + if strings.Contains(lockStr, "Playwright Output Directory") { + t.Error("Did not expect 'Playwright Output Directory' header in workflow without playwright") + } + + t.Logf("Successfully verified playwright output directory instructions are NOT included when playwright is disabled") +} + +func TestPlaywrightPromptOrderAfterTempFolder(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-playwright-order-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow with playwright + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: push +engine: claude +tools: + playwright: +--- + +# Test Workflow + +This is a test workflow to verify playwright instructions come after temp folder. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Find positions of temp folder and playwright instructions + tempFolderPos := strings.Index(lockStr, "Append temporary folder instructions to prompt") + playwrightPos := strings.Index(lockStr, "Append playwright output directory instructions to prompt") + + // Test: Verify playwright instructions come after temp folder instructions + if tempFolderPos == -1 { + t.Error("Expected temporary folder instructions in generated workflow") + } + + if playwrightPos == -1 { + t.Error("Expected playwright output directory instructions in generated workflow") + } + + if tempFolderPos != -1 && playwrightPos != -1 && playwrightPos <= tempFolderPos { + t.Errorf("Expected playwright instructions to come after temp folder instructions, but found at positions TempFolder=%d, Playwright=%d", tempFolderPos, playwrightPos) + } + + t.Logf("Successfully verified playwright instructions come after temp folder instructions in generated workflow") +} + +// ============================================================================ +// PR Context Prompt Tests +// ============================================================================ + +func TestPRContextPromptIncludedForIssueComment(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-pr-context-prompt-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow with issue_comment trigger + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: + issue_comment: + types: [created] +permissions: + contents: read +engine: claude +--- + +# Test Workflow with Issue Comment + +This is a test workflow with issue_comment trigger. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Test 1: Verify PR context prompt step is created + if !strings.Contains(lockStr, "- name: Append PR context instructions to prompt") { + t.Error("Expected 'Append PR context instructions to prompt' step in generated workflow") + } + + // Test 2: Verify the instruction mentions PR branch checkout + if !strings.Contains(lockStr, "pull request") { + t.Error("Expected 'pull request' reference in generated workflow") + } + + t.Logf("Successfully verified PR context instructions are included for issue_comment trigger") +} + +func TestPRContextPromptIncludedForCommand(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-pr-context-command-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow with command trigger + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: + command: + name: mybot +permissions: + contents: read +engine: claude +--- + +# Test Workflow with Command + +This is a test workflow with command trigger. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Test: Verify PR context prompt step is created for command triggers + if !strings.Contains(lockStr, "- name: Append PR context instructions to prompt") { + t.Error("Expected 'Append PR context instructions to prompt' step in workflow with command trigger") + } + + t.Logf("Successfully verified PR context instructions are included for command trigger") +} + +func TestPRContextPromptNotIncludedForPush(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-no-pr-context-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow with push trigger (no comment triggers) + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: push +permissions: + contents: read +engine: claude +--- + +# Test Workflow without Comment Triggers + +This is a test workflow with push trigger only. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Test: Verify PR context prompt step is NOT created for push triggers + if strings.Contains(lockStr, "- name: Append PR context instructions to prompt") { + t.Error("Did not expect 'Append PR context instructions to prompt' step for push trigger") + } + + t.Logf("Successfully verified PR context instructions are NOT included for push trigger") +} + +func TestPRContextPromptNotIncludedWithoutCheckout(t *testing.T) { + // Create a temporary directory for test files + tmpDir, err := os.MkdirTemp("", "gh-aw-pr-no-checkout-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create a test workflow with comment trigger but no checkout (no contents permission) + testFile := filepath.Join(tmpDir, "test-workflow.md") + testContent := `--- +on: + issue_comment: + types: [created] +permissions: + issues: read +engine: claude +--- + +# Test Workflow without Contents Permission + +This is a test workflow without contents read permission. +` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to create test workflow: %v", err) + } + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + if err := compiler.CompileWorkflow(testFile); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.Replace(testFile, ".md", ".lock.yml", 1) + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockStr := string(lockContent) + + // Test: Verify PR context prompt step is NOT created without contents permission + if strings.Contains(lockStr, "- name: Append PR context instructions to prompt") { + t.Error("Did not expect 'Append PR context instructions to prompt' step without contents read permission") + } + + t.Logf("Successfully verified PR context instructions are NOT included without contents permission") +} diff --git a/pkg/workflow/safe_outputs_prompt.go b/pkg/workflow/safe_outputs_prompt.go deleted file mode 100644 index 26f695f8242..00000000000 --- a/pkg/workflow/safe_outputs_prompt.go +++ /dev/null @@ -1,14 +0,0 @@ -package workflow - -import ( - "strings" -) - -// generateSafeOutputsPromptStep generates a separate step for safe outputs instructions -// This tells agents to use the safeoutputs MCP server instead of gh CLI -func (c *Compiler) generateSafeOutputsPromptStep(yaml *strings.Builder, hasSafeOutputs bool) { - generateStaticPromptStep(yaml, - "Append safe outputs instructions to prompt", - safeOutputsPromptText, - hasSafeOutputs) -} diff --git a/pkg/workflow/safe_outputs_prompt_test.go b/pkg/workflow/safe_outputs_prompt_test.go deleted file mode 100644 index b84ce71bbb2..00000000000 --- a/pkg/workflow/safe_outputs_prompt_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package workflow - -import ( - "strings" - "testing" -) - -func TestGenerateSafeOutputsPromptStep_IncludesWhenEnabled(t *testing.T) { - compiler := &Compiler{} - var yaml strings.Builder - - compiler.generateSafeOutputsPromptStep(&yaml, true) - - output := yaml.String() - if !strings.Contains(output, "Append safe outputs instructions to prompt") { - t.Error("Expected safe outputs prompt step to be generated when enabled") - } - if !strings.Contains(output, "safeoutputs MCP server") { - t.Error("Expected prompt to mention safeoutputs MCP server") - } - if !strings.Contains(output, "gh (GitHub CLI) command is NOT authenticated") { - t.Error("Expected prompt to warn about gh CLI not being authenticated") - } -} - -func TestGenerateSafeOutputsPromptStep_SkippedWhenDisabled(t *testing.T) { - compiler := &Compiler{} - var yaml strings.Builder - - compiler.generateSafeOutputsPromptStep(&yaml, false) - - output := yaml.String() - if strings.Contains(output, "safe outputs") { - t.Error("Expected safe outputs prompt step to NOT be generated when disabled") - } -} - -func TestSafeOutputsPromptText_FollowsXMLFormat(t *testing.T) { - if !strings.Contains(safeOutputsPromptText, "") { - t.Error("Expected prompt to start with XML tag") - } - if !strings.Contains(safeOutputsPromptText, "") { - t.Error("Expected prompt to end with XML tag") - } - if !strings.Contains(safeOutputsPromptText, "") { - t.Error("Expected prompt to contain section") - } - if !strings.Contains(safeOutputsPromptText, "") { - t.Error("Expected prompt to contain section") - } -}