diff --git a/pkg/workflow/compiler_activation_jobs_test.go b/pkg/workflow/compiler_activation_jobs_test.go new file mode 100644 index 0000000000..7ca0bc75ad --- /dev/null +++ b/pkg/workflow/compiler_activation_jobs_test.go @@ -0,0 +1,461 @@ +package workflow + +import ( + "strings" + "testing" + + "github.com/githubnext/gh-aw/pkg/constants" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestBuildPreActivationJob_WithPermissionCheck tests building pre-activation job with permission checks +func TestBuildPreActivationJob_WithPermissionCheck(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"test"}, + SafeOutputs: &SafeOutputsConfig{ + CreateIssues: &CreateIssuesConfig{}, + }, + } + + job, err := compiler.buildPreActivationJob(workflowData, true) + require.NoError(t, err, "buildPreActivationJob should succeed with permission check") + require.NotNil(t, job) + + assert.Equal(t, string(constants.PreActivationJobName), job.Name) + assert.NotNil(t, job.Outputs, "Job should have outputs") + + // Check for activated output + _, hasActivated := job.Outputs["activated"] + assert.True(t, hasActivated, "Job should have 'activated' output") + + // Check that steps contain membership check + stepsStr := strings.Join(job.Steps, "\n") + assert.Contains(t, stepsStr, constants.CheckMembershipStepID.String(), + "Steps should include membership check") +} + +// TestBuildPreActivationJob_WithoutPermissionCheck tests building pre-activation job without permission checks +func TestBuildPreActivationJob_WithoutPermissionCheck(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"test"}, + } + + job, err := compiler.buildPreActivationJob(workflowData, false) + require.NoError(t, err, "buildPreActivationJob should succeed without permission check") + require.NotNil(t, job) + + assert.Equal(t, string(constants.PreActivationJobName), job.Name) + + // Job should still have basic structure even without permission checks + assert.NotEmpty(t, job.Steps, "Job should have steps") +} + +// TestBuildPreActivationJob_WithStopTime tests building pre-activation job with stop-time validation +func TestBuildPreActivationJob_WithStopTime(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"test"}, + StopTime: "2024-12-31T23:59:59Z", + } + + job, err := compiler.buildPreActivationJob(workflowData, false) + require.NoError(t, err, "buildPreActivationJob should succeed with stop-time") + require.NotNil(t, job) + + // Check that steps contain stop-time check + stepsStr := strings.Join(job.Steps, "\n") + assert.Contains(t, stepsStr, constants.CheckStopTimeStepID.String(), + "Steps should include stop-time check") + assert.Contains(t, stepsStr, "GH_AW_STOP_TIME", + "Steps should include stop-time environment variable") + assert.Contains(t, stepsStr, workflowData.StopTime, + "Steps should include the actual stop-time value") +} + +// TestBuildPreActivationJob_WithReaction tests building pre-activation job with reaction +func TestBuildPreActivationJob_WithReaction(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + tests := []struct { + name string + reaction string + shouldHaveReaction bool + }{ + { + name: "with eyes reaction", + reaction: "eyes", + shouldHaveReaction: true, + }, + { + name: "with rocket reaction", + reaction: "rocket", + shouldHaveReaction: true, + }, + { + name: "with none reaction", + reaction: "none", + shouldHaveReaction: false, + }, + { + name: "empty reaction", + reaction: "", + shouldHaveReaction: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"test"}, + AIReaction: tt.reaction, + } + + job, err := compiler.buildPreActivationJob(workflowData, false) + require.NoError(t, err) + require.NotNil(t, job) + + stepsStr := strings.Join(job.Steps, "\n") + + if tt.shouldHaveReaction { + assert.Contains(t, stepsStr, "Add "+tt.reaction+" reaction", + "Steps should include reaction step for %s", tt.reaction) + assert.Contains(t, stepsStr, "GH_AW_REACTION", + "Steps should include reaction environment variable") + + // Check permissions include reaction permissions + assert.Contains(t, job.Permissions, "issues: write", + "Permissions should include issues: write for reactions") + } else { + // When reaction is "none" or empty, no reaction step should be added + if tt.reaction == "none" || tt.reaction == "" { + assert.NotContains(t, stepsStr, "GH_AW_REACTION", + "Steps should not include reaction for %s", tt.reaction) + } + } + }) + } +} + +// TestBuildPreActivationJob_WithCustomStepsAndOutputs tests custom steps/outputs extraction +func TestBuildPreActivationJob_WithCustomStepsAndOutputs(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + // Create workflow data with custom pre-activation job + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"test"}, + Jobs: map[string]any{ + "pre-activation": map[string]any{ + "steps": []any{ + map[string]any{ + "name": "Custom setup", + "run": "echo 'custom'", + }, + }, + "outputs": map[string]any{ + "custom_output": "${{ steps.custom.outputs.value }}", + }, + }, + }, + } + + job, err := compiler.buildPreActivationJob(workflowData, false) + require.NoError(t, err, "buildPreActivationJob should succeed with custom fields") + require.NotNil(t, job) + + // Check that custom steps are included + stepsStr := strings.Join(job.Steps, "\n") + assert.Contains(t, stepsStr, "Custom setup", "Should include custom step") + + // Check that custom outputs are included + _, hasCustomOutput := job.Outputs["custom_output"] + assert.True(t, hasCustomOutput, "Should include custom output") +} + +// TestBuildActivationJob_Basic tests building a basic activation job +func TestBuildActivationJob_Basic(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"echo", "test"}, + MarkdownContent: "# Test\n\nContent", + } + + job, err := compiler.buildActivationJob(workflowData, false, "", "test.lock.yml") + require.NoError(t, err, "buildActivationJob should succeed") + require.NotNil(t, job) + + assert.Equal(t, string(constants.ActivationJobName), job.Name) + assert.NotNil(t, job.Outputs, "Job should have outputs") +} + +// TestBuildActivationJob_WithPreActivation tests activation job when pre-activation exists +func TestBuildActivationJob_WithPreActivation(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"echo", "test"}, + MarkdownContent: "# Test\n\nContent", + } + + job, err := compiler.buildActivationJob(workflowData, true, "", "test.lock.yml") + require.NoError(t, err, "buildActivationJob should succeed with pre-activation") + require.NotNil(t, job) + + // When pre-activation exists, activation job should have needs dependency + assert.Contains(t, job.Needs, string(constants.PreActivationJobName), + "Activation job should depend on pre-activation job") +} + +// TestBuildActivationJob_WithReaction tests activation job with reaction configuration +func TestBuildActivationJob_WithReaction(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"echo", "test"}, + MarkdownContent: "# Test\n\nContent", + AIReaction: "rocket", + } + + job, err := compiler.buildActivationJob(workflowData, false, "", "test.lock.yml") + require.NoError(t, err) + require.NotNil(t, job) + + // Activation job should handle reactions appropriately + stepsStr := strings.Join(job.Steps, "\n") + // The reaction is actually added in pre-activation, but activation may reference it + assert.NotEmpty(t, stepsStr, "Activation job should have steps") +} + +// TestBuildMainJob_Basic tests building a basic main job +func TestBuildMainJob_Basic(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"echo", "test"}, + MarkdownContent: "# Test\n\nContent", + AI: "copilot", + } + + job, err := compiler.buildMainJob(workflowData, false) + require.NoError(t, err, "buildMainJob should succeed") + require.NotNil(t, job) + + assert.Equal(t, string(constants.AgentJobName), job.Name) + assert.NotEmpty(t, job.Steps, "Main job should have steps") +} + +// TestBuildMainJob_WithActivation tests main job when activation job exists +func TestBuildMainJob_WithActivation(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"echo", "test"}, + MarkdownContent: "# Test\n\nContent", + AI: "copilot", + } + + job, err := compiler.buildMainJob(workflowData, true) + require.NoError(t, err, "buildMainJob should succeed with activation") + require.NotNil(t, job) + + // When activation exists, main job should depend on it + assert.Contains(t, job.Needs, string(constants.ActivationJobName), + "Main job should depend on activation job") +} + +// TestBuildMainJob_WithPermissions tests main job permission handling +func TestBuildMainJob_WithPermissions(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"echo", "test"}, + MarkdownContent: "# Test\n\nContent", + AI: "copilot", + Permissions: "contents: read\nissues: write", + } + + job, err := compiler.buildMainJob(workflowData, false) + require.NoError(t, err) + require.NotNil(t, job) + + // Check permissions are set + assert.NotEmpty(t, job.Permissions, "Main job should have permissions") + assert.Contains(t, job.Permissions, "contents:", + "Permissions should include contents") +} + +// TestExtractPreActivationCustomFields_NoCustomJob tests extraction when no custom job exists +func TestExtractPreActivationCustomFields_NoCustomJob(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + jobs := map[string]any{ + "other-job": map[string]any{ + "runs-on": "ubuntu-latest", + }, + } + + steps, outputs, err := compiler.extractPreActivationCustomFields(jobs) + require.NoError(t, err) + assert.Empty(t, steps, "Should have no custom steps") + assert.Empty(t, outputs, "Should have no custom outputs") +} + +// TestExtractPreActivationCustomFields_WithCustomFields tests extraction with custom fields +func TestExtractPreActivationCustomFields_WithCustomFields(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + jobs := map[string]any{ + "pre-activation": map[string]any{ + "steps": []any{ + map[string]any{ + "name": "Custom step", + "run": "echo 'test'", + }, + }, + "outputs": map[string]any{ + "result": "${{ steps.test.outputs.value }}", + }, + }, + } + + steps, outputs, err := compiler.extractPreActivationCustomFields(jobs) + require.NoError(t, err) + assert.NotEmpty(t, steps, "Should have custom steps") + assert.NotEmpty(t, outputs, "Should have custom outputs") + + // Check step content + stepsStr := strings.Join(steps, "\n") + assert.Contains(t, stepsStr, "Custom step") + + // Check output content + result, hasResult := outputs["result"] + assert.True(t, hasResult, "Should have result output") + assert.Contains(t, result, "steps.test.outputs.value") +} + +// TestExtractPreActivationCustomFields_InvalidSteps tests error handling for invalid steps +func TestExtractPreActivationCustomFields_InvalidSteps(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + jobs := map[string]any{ + "pre-activation": map[string]any{ + "steps": "invalid", // Should be an array + }, + } + + steps, outputs, err := compiler.extractPreActivationCustomFields(jobs) + require.NoError(t, err, "Should handle invalid steps gracefully") + assert.Empty(t, steps, "Should have no steps with invalid format") + assert.Empty(t, outputs, "Should have no outputs with invalid format") +} + +// TestBuildPreActivationJob_Integration tests complete pre-activation job with multiple features +func TestBuildPreActivationJob_Integration(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + workflowData := &WorkflowData{ + Name: "Integration Test Workflow", + Command: []string{"test"}, + StopTime: "2024-12-31T23:59:59Z", + AIReaction: "eyes", + SafeOutputs: &SafeOutputsConfig{ + CreateIssues: &CreateIssuesConfig{}, + }, + } + + job, err := compiler.buildPreActivationJob(workflowData, true) + require.NoError(t, err, "buildPreActivationJob should succeed with all features") + require.NotNil(t, job) + + stepsStr := strings.Join(job.Steps, "\n") + + // Should have all features + assert.Contains(t, stepsStr, "setup", "Should include setup step") + assert.Contains(t, stepsStr, constants.CheckMembershipStepID.String(), "Should include membership check") + assert.Contains(t, stepsStr, constants.CheckStopTimeStepID.String(), "Should include stop-time check") + assert.Contains(t, stepsStr, "eyes", "Should include reaction") + + // Should have proper permissions + assert.Contains(t, job.Permissions, "issues: write", "Should have issues write permission") + assert.Contains(t, job.Permissions, "pull-requests: write", "Should have PR write permission") + + // Should have activated output + _, hasActivated := job.Outputs["activated"] + assert.True(t, hasActivated, "Should have activated output") +} + +// TestBuildActivationJob_WithWorkflowRunRepoSafety tests activation with workflow_run repo safety +func TestBuildActivationJob_WithWorkflowRunRepoSafety(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"echo", "test"}, + MarkdownContent: "# Test\n\nContent", + } + + // Test with workflow_run repo safety enabled + job, err := compiler.buildActivationJob(workflowData, false, "workflow_run", "test.lock.yml") + require.NoError(t, err) + require.NotNil(t, job) + + stepsStr := strings.Join(job.Steps, "\n") + // Should include repository validation for workflow_run + assert.NotEmpty(t, stepsStr) +} + +// TestBuildMainJob_EngineSpecific tests main job with different engines +func TestBuildMainJob_EngineSpecific(t *testing.T) { + tests := []struct { + name string + engine string + }{ + { + name: "copilot engine", + engine: "copilot", + }, + { + name: "claude engine", + engine: "claude", + }, + { + name: "codex engine", + engine: "codex", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"echo", "test"}, + MarkdownContent: "# Test\n\nContent", + AI: tt.engine, + } + + job, err := compiler.buildMainJob(workflowData, false) + require.NoError(t, err, "buildMainJob should succeed for engine %s", tt.engine) + require.NotNil(t, job) + assert.NotEmpty(t, job.Steps, "Should have steps for engine %s", tt.engine) + }) + } +} diff --git a/pkg/workflow/compiler_orchestrator_test.go b/pkg/workflow/compiler_orchestrator_test.go new file mode 100644 index 0000000000..8498050999 --- /dev/null +++ b/pkg/workflow/compiler_orchestrator_test.go @@ -0,0 +1,489 @@ +package workflow + +import ( + "os" + "path/filepath" + "testing" + + "github.com/githubnext/gh-aw/pkg/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestParseWorkflowFile_ValidMainWorkflow tests parsing a valid main workflow +func TestParseWorkflowFile_ValidMainWorkflow(t *testing.T) { + tmpDir := testutil.TempDir(t, "parse-valid-main") + + testContent := `--- +on: push +engine: copilot +timeout-minutes: 10 +strict: false +features: + dangerous-permissions-write: true +permissions: + contents: read +--- + +# Test Main Workflow + +This is a valid main workflow with an 'on' field. +` + + testFile := filepath.Join(tmpDir, "main-workflow.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + workflowData, err := compiler.ParseWorkflowFile(testFile) + require.NoError(t, err, "Valid main workflow should parse successfully") + require.NotNil(t, workflowData, "WorkflowData should not be nil") + + // Verify parsed data + assert.Equal(t, "# Test Main Workflow\n\nThis is a valid main workflow with an 'on' field.\n", workflowData.MarkdownContent) +} + +// TestParseWorkflowFile_SharedWorkflow tests parsing a shared/imported workflow (no 'on' field) +func TestParseWorkflowFile_SharedWorkflow(t *testing.T) { + tmpDir := testutil.TempDir(t, "parse-shared") + + // Shared workflows don't have 'on' field + testContent := `--- +engine: copilot +permissions: + contents: read +--- + +# Shared Workflow + +This can be imported by other workflows. +` + + testFile := filepath.Join(tmpDir, "shared-workflow.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + workflowData, err := compiler.ParseWorkflowFile(testFile) + + // Should return SharedWorkflowError + require.Error(t, err, "Shared workflow should return an error") + assert.Nil(t, workflowData, "WorkflowData should be nil for shared workflows") + + // Check if it's a SharedWorkflowError + var sharedErr *SharedWorkflowError + require.ErrorAs(t, err, &sharedErr, "Error should be SharedWorkflowError type") + assert.Equal(t, testFile, sharedErr.Path) +} + +// TestParseWorkflowFile_MissingFrontmatter tests error handling for missing frontmatter +func TestParseWorkflowFile_MissingFrontmatter(t *testing.T) { + tmpDir := testutil.TempDir(t, "parse-no-frontmatter") + + testContent := `# Workflow Without Frontmatter + +This file has no frontmatter section. +` + + testFile := filepath.Join(tmpDir, "no-frontmatter.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + workflowData, err := compiler.ParseWorkflowFile(testFile) + + require.Error(t, err, "Should error when frontmatter is missing") + assert.Nil(t, workflowData) + assert.Contains(t, err.Error(), "frontmatter", "Error should mention frontmatter") +} + +// TestParseWorkflowFile_InvalidYAML tests error handling for invalid YAML frontmatter +func TestParseWorkflowFile_InvalidYAML(t *testing.T) { + tmpDir := testutil.TempDir(t, "parse-invalid-yaml") + + testContent := `--- +on: push +invalid: [unclosed +bracket: here +--- + +# Workflow + +Content +` + + testFile := filepath.Join(tmpDir, "invalid-yaml.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + workflowData, err := compiler.ParseWorkflowFile(testFile) + + require.Error(t, err, "Should error with invalid YAML") + assert.Nil(t, workflowData) +} + +// TestParseWorkflowFile_PathTraversal tests path traversal protection +func TestParseWorkflowFile_PathTraversal(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + // Try various path traversal patterns + pathsToTest := []string{ + "../../../etc/passwd", + "./../../etc/passwd", + ".../.../etc/passwd", + } + + for _, path := range pathsToTest { + _, err := compiler.ParseWorkflowFile(path) + // Should fail (file doesn't exist or is rejected) + require.Error(t, err, "Path traversal attempt should fail: %s", path) + } +} + +// TestParseWorkflowFile_NoMarkdownContent tests error handling for main workflows without markdown content +func TestParseWorkflowFile_NoMarkdownContent(t *testing.T) { + tmpDir := testutil.TempDir(t, "parse-no-markdown") + + // Main workflow (has 'on' field) but no markdown content + testContent := `--- +on: push +engine: copilot +--- +` + + testFile := filepath.Join(tmpDir, "no-markdown.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + workflowData, err := compiler.ParseWorkflowFile(testFile) + + require.Error(t, err, "Main workflow without markdown content should error") + assert.Nil(t, workflowData) + assert.Contains(t, err.Error(), "markdown content", "Error should mention markdown content") +} + +// TestParseWorkflowFile_EngineExtraction tests engine config extraction +func TestParseWorkflowFile_EngineExtraction(t *testing.T) { + tmpDir := testutil.TempDir(t, "parse-engine") + + tests := []struct { + name string + frontmatter string + expectedEngine string + }{ + { + name: "copilot engine", + frontmatter: `--- +on: push +engine: copilot +---`, + expectedEngine: "copilot", + }, + { + name: "claude engine", + frontmatter: `--- +on: push +engine: claude +---`, + expectedEngine: "claude", + }, + { + name: "default engine when not specified", + frontmatter: `--- +on: push +---`, + expectedEngine: "copilot", // Default engine + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testContent := tt.frontmatter + "\n\n# Workflow\n\nContent\n" + testFile := filepath.Join(tmpDir, "engine-test-"+tt.name+".md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + workflowData, err := compiler.ParseWorkflowFile(testFile) + require.NoError(t, err) + require.NotNil(t, workflowData) + + // Check engine via AI field (backwards compatibility) or EngineConfig + actualEngine := workflowData.AI + if workflowData.EngineConfig != nil && workflowData.EngineConfig.ID != "" { + actualEngine = workflowData.EngineConfig.ID + } + assert.Equal(t, tt.expectedEngine, actualEngine, + "Engine should be %s for test %s", tt.expectedEngine, tt.name) + }) + } +} + +// TestParseWorkflowFile_EngineOverride tests command-line engine override +func TestParseWorkflowFile_EngineOverride(t *testing.T) { + tmpDir := testutil.TempDir(t, "parse-engine-override") + + testContent := `--- +on: push +engine: copilot +--- + +# Workflow + +Content +` + + testFile := filepath.Join(tmpDir, "override-test.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + // Create compiler with engine override + compiler := NewCompiler(false, "claude", "test") + workflowData, err := compiler.ParseWorkflowFile(testFile) + require.NoError(t, err) + require.NotNil(t, workflowData) + + // Engine should be overridden to 'claude' (stored in AI field for backward compatibility) + assert.Equal(t, "claude", workflowData.AI, "Engine should be overridden to claude") +} + +// TestParseWorkflowFile_NetworkPermissions tests network permissions extraction +func TestParseWorkflowFile_NetworkPermissions(t *testing.T) { + tmpDir := testutil.TempDir(t, "parse-network") + + tests := []struct { + name string + includeNetwork bool + networkConfig string + expectedMode string + expectedHasAllowed bool + }{ + { + name: "default network mode", + includeNetwork: false, + expectedMode: "defaults", + }, + { + name: "explicit allowed domains", + includeNetwork: true, + networkConfig: ` +network: + allowed: + - github.com + - api.example.com`, + expectedMode: "defaults", + expectedHasAllowed: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + frontmatter := "---\non: push\nengine: copilot" + if tt.includeNetwork { + frontmatter += "\n" + tt.networkConfig + } + frontmatter += "\n---" + + testContent := frontmatter + "\n\n# Workflow\n\nContent\n" + testFile := filepath.Join(tmpDir, "network-test-"+tt.name+".md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + workflowData, err := compiler.ParseWorkflowFile(testFile) + require.NoError(t, err) + require.NotNil(t, workflowData) + require.NotNil(t, workflowData.NetworkPermissions) + + assert.Equal(t, tt.expectedMode, workflowData.NetworkPermissions.Mode, + "Network mode should be %s", tt.expectedMode) + + if tt.expectedHasAllowed { + assert.NotEmpty(t, workflowData.NetworkPermissions.Allowed, + "Should have allowed domains") + } + }) + } +} + +// TestParseWorkflowFile_StrictMode tests strict mode validation +func TestParseWorkflowFile_StrictMode(t *testing.T) { + tmpDir := testutil.TempDir(t, "parse-strict") + + tests := []struct { + name string + cliStrict bool + yamlStrict *bool // nil means not specified in YAML + expectError bool + }{ + { + name: "strict mode default (true)", + cliStrict: false, + yamlStrict: nil, + expectError: false, + }, + { + name: "strict mode explicitly true", + cliStrict: false, + yamlStrict: ptrBool(true), + expectError: false, + }, + { + name: "strict mode explicitly false", + cliStrict: false, + yamlStrict: ptrBool(false), + expectError: false, + }, + { + name: "cli strict mode overrides yaml", + cliStrict: true, + yamlStrict: ptrBool(false), + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + frontmatter := "---\non: push\nengine: copilot" + if tt.yamlStrict != nil { + if *tt.yamlStrict { + frontmatter += "\nstrict: true" + } else { + frontmatter += "\nstrict: false\nfeatures:\n dangerous-permissions-write: true" + } + } + frontmatter += "\n---" + + testContent := frontmatter + "\n\n# Workflow\n\nContent\n" + testFile := filepath.Join(tmpDir, "strict-test-"+tt.name+".md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(tt.cliStrict, "", "test") + _, err := compiler.ParseWorkflowFile(testFile) + + if tt.expectError { + require.Error(t, err, "Should error in strict mode test: %s", tt.name) + } else { + require.NoError(t, err, "Should not error in strict mode test: %s", tt.name) + } + }) + } +} + +// ptrBool returns a pointer to a boolean value +func ptrBool(b bool) *bool { + return &b +} + +// TestCopyFrontmatterWithoutInternalMarkers tests internal marker removal +func TestCopyFrontmatterWithoutInternalMarkers(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + tests := []struct { + name string + input map[string]any + expected map[string]any + }{ + { + name: "no internal markers", + input: map[string]any{ + "on": "push", + "engine": "copilot", + }, + expected: map[string]any{ + "on": "push", + "engine": "copilot", + }, + }, + { + name: "gh_aw_native_label_filter marker removed", + input: map[string]any{ + "on": map[string]any{ + "issues": map[string]any{ + "types": []string{"opened"}, + "__gh_aw_native_label_filter__": true, + }, + }, + "engine": "copilot", + }, + expected: map[string]any{ + "on": map[string]any{ + "issues": map[string]any{ + "types": []string{"opened"}, + }, + }, + "engine": "copilot", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := compiler.copyFrontmatterWithoutInternalMarkers(tt.input) + + // Check all expected keys exist + for key, expectedVal := range tt.expected { + actualVal, exists := result[key] + assert.True(t, exists, "Key %s should exist in result", key) + + // For nested maps, check recursively + if expectedMap, ok := expectedVal.(map[string]any); ok { + actualMap, ok := actualVal.(map[string]any) + require.True(t, ok, "Value for %s should be a map", key) + for nestedKey := range expectedMap { + _, exists := actualMap[nestedKey] + assert.True(t, exists, "Nested key %s.%s should exist", key, nestedKey) + } + } + } + + // Check that specific marker was removed + if onMap, ok := result["on"].(map[string]any); ok { + if issuesMap, ok := onMap["issues"].(map[string]any); ok { + _, exists := issuesMap["__gh_aw_native_label_filter__"] + assert.False(t, exists, "Internal marker __gh_aw_native_label_filter__ should be removed") + } + } + }) + } +} + +// TestDetectTextOutputUsageInOrchestrator tests text output detection in markdown +func TestDetectTextOutputUsageInOrchestrator(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + tests := []struct { + name string + markdown string + expectedOutput bool + }{ + { + name: "no text output", + markdown: "# Workflow\n\nSimple workflow with no output markers.", + expectedOutput: false, + }, + { + name: "with text output usage", + markdown: "# Workflow\n\nUse ${{ needs.activation.outputs.text }} here.", + expectedOutput: true, + }, + { + name: "text output in middle", + markdown: "# Start\n\nContent\n${{ needs.activation.outputs.text }}\n\nMore content", + expectedOutput: true, + }, + { + name: "multiple text output references", + markdown: "${{ needs.activation.outputs.text }}\nFirst\n${{ needs.activation.outputs.text }}\nSecond", + expectedOutput: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := compiler.detectTextOutputUsage(tt.markdown) + assert.Equal(t, tt.expectedOutput, result, + "Text output detection should return %v for test %s", tt.expectedOutput, tt.name) + }) + } +} + +// Helper functions + +func hasInternalPrefix(key string) bool { + return len(key) > 2 && key[0] == '_' && key[1] == '_' +} diff --git a/pkg/workflow/compiler_test.go b/pkg/workflow/compiler_test.go new file mode 100644 index 0000000000..4cce69f53c --- /dev/null +++ b/pkg/workflow/compiler_test.go @@ -0,0 +1,328 @@ +package workflow + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/githubnext/gh-aw/pkg/stringutil" + "github.com/githubnext/gh-aw/pkg/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCompileWorkflow_ValidWorkflow tests successful compilation of a valid workflow +func TestCompileWorkflow_ValidWorkflow(t *testing.T) { + tmpDir := testutil.TempDir(t, "compiler-test") + + testContent := `--- +on: push +timeout-minutes: 10 +permissions: + contents: read + pull-requests: read +engine: copilot +strict: false +features: + dangerous-permissions-write: true +tools: + github: + allowed: [list_issues, create_issue] + bash: ["echo", "ls"] +--- + +# Test Workflow + +This is a test workflow for compilation. +` + + testFile := filepath.Join(tmpDir, "test-workflow.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + err := compiler.CompileWorkflow(testFile) + require.NoError(t, err, "Valid workflow should compile without errors") + + // Verify lock file was created + lockFile := stringutil.MarkdownToLockFile(testFile) + _, err = os.Stat(lockFile) + require.NoError(t, err, "Lock file should be created") + + // Verify lock file contains expected content + lockContent, err := os.ReadFile(lockFile) + require.NoError(t, err) + lockStr := string(lockContent) + + // Verify basic workflow structure + assert.Contains(t, lockStr, "name:", "Lock file should contain workflow name") + assert.Contains(t, lockStr, "on:", "Lock file should contain 'on' trigger") + assert.Contains(t, lockStr, "jobs:", "Lock file should contain jobs section") +} + +// TestCompileWorkflow_NonexistentFile tests error handling for missing files +func TestCompileWorkflow_NonexistentFile(t *testing.T) { + compiler := NewCompiler(false, "", "test") + err := compiler.CompileWorkflow("/nonexistent/file.md") + require.Error(t, err, "Should error with nonexistent file") + assert.Contains(t, err.Error(), "failed to read file", "Error should mention file read failure") +} + +// TestCompileWorkflow_EmptyPath tests error handling for empty path +func TestCompileWorkflow_EmptyPath(t *testing.T) { + compiler := NewCompiler(false, "", "test") + err := compiler.CompileWorkflow("") + require.Error(t, err, "Should error with empty path") +} + +// TestCompileWorkflow_MissingFrontmatter tests error handling for files without frontmatter +func TestCompileWorkflow_MissingFrontmatter(t *testing.T) { + tmpDir := testutil.TempDir(t, "compiler-missing-frontmatter") + + // File with no frontmatter + testContent := `# Test Workflow + +This workflow has no frontmatter. +` + + testFile := filepath.Join(tmpDir, "no-frontmatter.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + err := compiler.CompileWorkflow(testFile) + require.Error(t, err, "Should error when frontmatter is missing") + assert.Contains(t, err.Error(), "frontmatter", "Error should mention frontmatter") +} + +// TestCompileWorkflow_InvalidFrontmatter tests error handling for invalid YAML frontmatter +func TestCompileWorkflow_InvalidFrontmatter(t *testing.T) { + tmpDir := testutil.TempDir(t, "compiler-invalid-frontmatter") + + // Invalid YAML in frontmatter + testContent := `--- +on: push +invalid yaml: [unclosed bracket +--- + +# Test Workflow + +Content here. +` + + testFile := filepath.Join(tmpDir, "invalid-frontmatter.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + err := compiler.CompileWorkflow(testFile) + require.Error(t, err, "Should error with invalid YAML frontmatter") +} + +// TestCompileWorkflow_MissingMarkdownContent tests error handling for workflows with no markdown content +func TestCompileWorkflow_MissingMarkdownContent(t *testing.T) { + tmpDir := testutil.TempDir(t, "compiler-no-markdown") + + // Frontmatter only, no markdown + testContent := `--- +on: push +engine: copilot +--- +` + + testFile := filepath.Join(tmpDir, "no-markdown.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + err := compiler.CompileWorkflow(testFile) + require.Error(t, err, "Should error when markdown content is missing") + assert.Contains(t, err.Error(), "markdown content", "Error should mention markdown content") +} + +// TestCompileWorkflow_CampaignOrchestrator tests lock file naming for campaign orchestrators +func TestCompileWorkflow_CampaignOrchestrator(t *testing.T) { + tmpDir := testutil.TempDir(t, "compiler-campaign") + + testContent := `--- +on: push +engine: copilot +strict: false +features: + dangerous-permissions-write: true +--- + +# Campaign Test + +Campaign orchestrator workflow. +` + + // Create a campaign orchestrator file (*.campaign.g.md) + testFile := filepath.Join(tmpDir, "test.campaign.g.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + err := compiler.CompileWorkflow(testFile) + require.NoError(t, err, "Campaign orchestrator should compile") + + // Verify lock file has correct name: test.campaign.lock.yml (not test.campaign.g.lock.yml) + expectedLockFile := filepath.Join(tmpDir, "test.campaign.lock.yml") + _, err = os.Stat(expectedLockFile) + require.NoError(t, err, "Campaign lock file should be created with correct name") +} + +// TestCompileWorkflowData_Success tests CompileWorkflowData with valid workflow data +func TestCompileWorkflowData_Success(t *testing.T) { + tmpDir := testutil.TempDir(t, "compiler-data-test") + + workflowData := &WorkflowData{ + Name: "Test Workflow", + Command: []string{"echo", "test"}, + MarkdownContent: "# Test\n\nTest content", + AI: "copilot", + } + + markdownPath := filepath.Join(tmpDir, "test.md") + // Create the markdown file (needed for lock file generation) + testContent := `--- +on: push +engine: copilot +--- + +# Test + +Test content +` + require.NoError(t, os.WriteFile(markdownPath, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + err := compiler.CompileWorkflowData(workflowData, markdownPath) + require.NoError(t, err, "CompileWorkflowData should succeed with valid data") + + // Verify lock file was created + lockFile := stringutil.MarkdownToLockFile(markdownPath) + _, err = os.Stat(lockFile) + require.NoError(t, err, "Lock file should be created") +} + +// TestCompileWorkflow_LockFileSize tests that generated lock files don't exceed size limits +func TestCompileWorkflow_LockFileSize(t *testing.T) { + tmpDir := testutil.TempDir(t, "compiler-size-test") + + testContent := `--- +on: push +engine: copilot +strict: false +features: + dangerous-permissions-write: true +--- + +# Size Test Workflow + +This is a normal workflow that should generate a reasonable-sized lock file. +` + + testFile := filepath.Join(tmpDir, "size-test.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + err := compiler.CompileWorkflow(testFile) + require.NoError(t, err, "Workflow should compile") + + // Check lock file size + lockFile := stringutil.MarkdownToLockFile(testFile) + info, err := os.Stat(lockFile) + require.NoError(t, err) + + // Verify size is reasonable (under MaxLockFileSize) + assert.LessOrEqual(t, info.Size(), int64(MaxLockFileSize), + "Lock file should not exceed MaxLockFileSize (%d bytes)", MaxLockFileSize) +} + +// TestCompileWorkflow_ErrorFormatting tests that compilation errors are properly formatted +func TestCompileWorkflow_ErrorFormatting(t *testing.T) { + tmpDir := testutil.TempDir(t, "compiler-error-format") + + // Create a workflow with a validation error (missing required 'on' field in main workflow) + testContent := `--- +engine: copilot +--- + +# Invalid Workflow + +This workflow is missing the required 'on' field. +` + + testFile := filepath.Join(tmpDir, "invalid.md") + require.NoError(t, os.WriteFile(testFile, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + err := compiler.CompileWorkflow(testFile) + require.Error(t, err, "Should error with validation issues") + + // Error should contain file reference + errorStr := err.Error() + assert.True(t, strings.Contains(errorStr, "invalid.md") || strings.Contains(errorStr, "error"), + "Error should reference the file or contain 'error'") +} + +// TestCompileWorkflow_PathTraversal tests that path traversal attempts are handled safely +func TestCompileWorkflow_PathTraversal(t *testing.T) { + compiler := NewCompiler(false, "", "test") + + // Try a path with traversal elements + err := compiler.CompileWorkflow("../../etc/passwd") + require.Error(t, err, "Should error (file doesn't exist or is rejected)") +} + +// TestCompileWorkflowData_ArtifactManagerReset tests that artifact manager is reset between compilations +func TestCompileWorkflowData_ArtifactManagerReset(t *testing.T) { + tmpDir := testutil.TempDir(t, "compiler-artifact-reset") + + workflowData := &WorkflowData{ + Name: "Test Workflow 1", + Command: []string{"echo", "test"}, + MarkdownContent: "# Test 1", + AI: "copilot", + } + + markdownPath := filepath.Join(tmpDir, "test1.md") + testContent := `--- +on: push +engine: copilot +--- + +# Test 1 +` + require.NoError(t, os.WriteFile(markdownPath, []byte(testContent), 0644)) + + compiler := NewCompiler(false, "", "test") + + // First compilation + err := compiler.CompileWorkflowData(workflowData, markdownPath) + require.NoError(t, err) + + // Artifact manager should exist + require.NotNil(t, compiler.artifactManager, "Artifact manager should be initialized") + + // Second compilation with different data + workflowData2 := &WorkflowData{ + Name: "Test Workflow 2", + Command: []string{"echo", "test2"}, + MarkdownContent: "# Test 2", + AI: "copilot", + } + + markdownPath2 := filepath.Join(tmpDir, "test2.md") + testContent2 := `--- +on: push +engine: copilot +--- + +# Test 2 +` + require.NoError(t, os.WriteFile(markdownPath2, []byte(testContent2), 0644)) + + err = compiler.CompileWorkflowData(workflowData2, markdownPath2) + require.NoError(t, err) + + // Artifact manager should still exist (it's reset, not recreated to nil) + require.NotNil(t, compiler.artifactManager, "Artifact manager should persist after reset") +} diff --git a/specs/artifacts.md b/specs/artifacts.md index 2371f70cd9..02a41bfbf3 100644 --- a/specs/artifacts.md +++ b/specs/artifacts.md @@ -24,10 +24,10 @@ This section provides an overview of artifacts organized by job name, with dupli - `agent-artifacts` - **Paths**: `/tmp/gh-aw/agent-stdio.log`, `/tmp/gh-aw/aw-prompts/prompt.txt`, `/tmp/gh-aw/aw.patch`, `/tmp/gh-aw/aw_info.json`, `/tmp/gh-aw/mcp-logs/`, `/tmp/gh-aw/safe-inputs/logs/`, `/tmp/gh-aw/sandbox/firewall/logs/` - - **Used in**: 69 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, ai-moderator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, codex-github-remote-mcp-test.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, example-custom-error-patterns.md, example-permissions-warning.md, firewall.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, metrics-collector.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md + - **Used in**: 70 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, agentic-campaign-generator.md, ai-moderator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, codex-github-remote-mcp-test.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, example-custom-error-patterns.md, example-permissions-warning.md, firewall.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, metrics-collector.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md - `agent-output` - **Paths**: `${{ env.GH_AW_AGENT_OUTPUT }}` - - **Used in**: 64 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, ai-moderator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md + - **Used in**: 65 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, agentic-campaign-generator.md, ai-moderator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md - `agent_outputs` - **Paths**: `/tmp/gh-aw/mcp-config/logs/`, `/tmp/gh-aw/redacted-urls.log`, `/tmp/gh-aw/sandbox/agent/logs/` - **Used in**: 60 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, ai-moderator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, code-scanning-fixer.md, codex-github-remote-mcp-test.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, example-custom-error-patterns.md, example-permissions-warning.md, firewall.md, glossary-maintainer.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, metrics-collector.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md @@ -51,7 +51,7 @@ This section provides an overview of artifacts organized by job name, with dupli - **Used in**: 8 workflow(s) - agent-performance-analyzer.md, copilot-pr-nlp-analysis.md, daily-copilot-token-report.md, daily-news.md, deep-report.md, metrics-collector.md, security-compliance.md, workflow-health-manager.md - `safe-output` - **Paths**: `${{ env.GH_AW_SAFE_OUTPUTS }}` - - **Used in**: 64 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, ai-moderator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md + - **Used in**: 65 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, agentic-campaign-generator.md, ai-moderator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md - `safe-outputs-assets` - **Paths**: `/tmp/gh-aw/safeoutputs/assets/` - **Used in**: 12 workflow(s) - copilot-pr-nlp-analysis.md, daily-copilot-token-report.md, daily-issues-report.md, daily-news.md, daily-repo-chronicle.md, deep-report.md, github-mcp-structural-analysis.md, poem-bot.md, python-data-charts.md, stale-repo-identifier.md, technical-doc-writer.md, weekly-issue-summary.md @@ -74,7 +74,7 @@ This section provides an overview of artifacts organized by job name, with dupli - `agent-output` - **Download paths**: `/tmp/gh-aw/safeoutputs/` - - **Used in**: 64 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, ai-moderator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md + - **Used in**: 65 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, agentic-campaign-generator.md, ai-moderator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md ### Job: `detection` @@ -82,16 +82,16 @@ This section provides an overview of artifacts organized by job name, with dupli - `threat-detection.log` - **Paths**: `/tmp/gh-aw/threat-detection/detection.log` - - **Used in**: 63 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md + - **Used in**: 64 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, agentic-campaign-generator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md **Artifacts Downloaded:** - `agent-artifacts` - **Download paths**: `/tmp/gh-aw/threat-detection/` - - **Used in**: 63 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md + - **Used in**: 64 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, agentic-campaign-generator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md - `agent-output` - **Download paths**: `/tmp/gh-aw/threat-detection/` - - **Used in**: 63 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md + - **Used in**: 64 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, agentic-campaign-generator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md ### Job: `notion_add_comment` @@ -126,7 +126,7 @@ This section provides an overview of artifacts organized by job name, with dupli - **Used in**: 15 workflow(s) - changeset.md, ci-coach.md, cloclo.md, code-scanning-fixer.md, craft.md, dictation-prompt.md, glossary-maintainer.md, hourly-ci-cleaner.md, layout-spec-maintainer.md, mergefest.md, poem-bot.md, q.md, slide-deck-maintainer.md, technical-doc-writer.md, tidy.md - `agent-output` - **Download paths**: `/tmp/gh-aw/safeoutputs/` - - **Used in**: 64 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, ai-moderator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md + - **Used in**: 65 workflow(s) - agent-performance-analyzer.md, agent-persona-explorer.md, agentic-campaign-generator.md, ai-moderator.md, archie.md, brave.md, breaking-change-checker.md, changeset.md, ci-coach.md, ci-doctor.md, cli-consistency-checker.md, cloclo.md, code-scanning-fixer.md, commit-changes-analyzer.md, copilot-pr-merged-report.md, copilot-pr-nlp-analysis.md, craft.md, daily-choice-test.md, daily-copilot-token-report.md, daily-fact.md, daily-file-diet.md, daily-issues-report.md, daily-news.md, daily-observability-report.md, daily-repo-chronicle.md, daily-team-status.md, deep-report.md, dependabot-go-checker.md, dev-hawk.md, dev.md, dictation-prompt.md, github-mcp-structural-analysis.md, glossary-maintainer.md, go-fan.md, go-pattern-detector.md, grumpy-reviewer.md, hourly-ci-cleaner.md, issue-classifier.md, issue-triage-agent.md, layout-spec-maintainer.md, mergefest.md, notion-issue-summary.md, pdf-summary.md, plan.md, poem-bot.md, pr-nitpick-reviewer.md, python-data-charts.md, q.md, release.md, repo-audit-analyzer.md, repository-quality-improver.md, research.md, scout.md, security-compliance.md, security-review.md, slide-deck-maintainer.md, stale-repo-identifier.md, super-linter.md, technical-doc-writer.md, tidy.md, typist.md, video-analyzer.md, weekly-issue-summary.md, workflow-generator.md, workflow-health-manager.md ### Job: `super_linter` @@ -317,6 +317,62 @@ This section provides an overview of artifacts organized by job name, with dupli - **Download path**: `/tmp/gh-aw/cache-memory` - **Depends on jobs**: [agent detection] +### agentic-campaign-generator.md + +#### Job: `agent` + +**Uploads:** + +- **Artifact**: `safe-output` + - **Upload paths**: + - `${{ env.GH_AW_SAFE_OUTPUTS }}` + +- **Artifact**: `agent-output` + - **Upload paths**: + - `${{ env.GH_AW_AGENT_OUTPUT }}` + +- **Artifact**: `agent-artifacts` + - **Upload paths**: + - `/tmp/gh-aw/aw-prompts/prompt.txt` + - `/tmp/gh-aw/aw_info.json` + - `/tmp/gh-aw/mcp-logs/` + - `/tmp/gh-aw/sandbox/firewall/logs/` + - `/tmp/gh-aw/agent-stdio.log` + +#### Job: `conclusion` + +**Downloads:** + +- **Artifact**: `agent-output` (by name) + - **Download path**: `/tmp/gh-aw/safeoutputs/` + - **Depends on jobs**: [activation agent detection safe_outputs] + +#### Job: `detection` + +**Uploads:** + +- **Artifact**: `threat-detection.log` + - **Upload paths**: + - `/tmp/gh-aw/threat-detection/detection.log` + +**Downloads:** + +- **Artifact**: `agent-artifacts` (by name) + - **Download path**: `/tmp/gh-aw/threat-detection/` + - **Depends on jobs**: [agent] + +- **Artifact**: `agent-output` (by name) + - **Download path**: `/tmp/gh-aw/threat-detection/` + - **Depends on jobs**: [agent] + +#### Job: `safe_outputs` + +**Downloads:** + +- **Artifact**: `agent-output` (by name) + - **Download path**: `/tmp/gh-aw/safeoutputs/` + - **Depends on jobs**: [activation agent detection] + ### ai-moderator.md #### Job: `agent`