From aa228a2fcb174589967db8644f2c75847f4eb4c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 17:58:58 +0000 Subject: [PATCH 1/4] Initial plan From 3b7a469f265901e4b3d76e3f600cc001e78baa3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 18:11:54 +0000 Subject: [PATCH 2/4] Add helper functions to filter out README.md files from workflow discovery Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/compile_helpers.go | 3 + pkg/cli/compile_orchestration.go | 3 + pkg/cli/init.go | 3 + pkg/cli/mcp_workflow_scanner.go | 3 + pkg/cli/remove_command.go | 3 + pkg/cli/status_command.go | 3 + pkg/cli/workflows.go | 18 +++ pkg/cli/workflows_test.go | 235 +++++++++++++++++++++++++++++++ 8 files changed, 271 insertions(+) create mode 100644 pkg/cli/workflows_test.go diff --git a/pkg/cli/compile_helpers.go b/pkg/cli/compile_helpers.go index ddf58ff55d..f7f60ee6ab 100644 --- a/pkg/cli/compile_helpers.go +++ b/pkg/cli/compile_helpers.go @@ -154,6 +154,9 @@ func compileAllWorkflowFiles(compiler *workflow.Compiler, workflowsDir string, v return stats, fmt.Errorf("failed to find markdown files: %w", err) } + // Filter out README.md files + mdFiles = filterWorkflowFiles(mdFiles) + if len(mdFiles) == 0 { compileHelpersLog.Printf("No markdown files found in %s", workflowsDir) if verbose { diff --git a/pkg/cli/compile_orchestration.go b/pkg/cli/compile_orchestration.go index d4d3af76d1..187fe18538 100644 --- a/pkg/cli/compile_orchestration.go +++ b/pkg/cli/compile_orchestration.go @@ -241,6 +241,9 @@ func compileAllFilesInDirectory( return nil, fmt.Errorf("failed to find markdown files: %w", err) } + // Filter out README.md files + mdFiles = filterWorkflowFiles(mdFiles) + if len(mdFiles) == 0 { return nil, fmt.Errorf("no markdown files found in %s", workflowsDir) } diff --git a/pkg/cli/init.go b/pkg/cli/init.go index af12f95652..240931fd08 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -557,6 +557,9 @@ func ensureMaintenanceWorkflow(verbose bool) error { return fmt.Errorf("failed to find workflow files: %w", err) } + // Filter out README.md files + files = filterWorkflowFiles(files) + // Create a compiler to parse workflows compiler := workflow.NewCompiler(false, "", GetVersion()) diff --git a/pkg/cli/mcp_workflow_scanner.go b/pkg/cli/mcp_workflow_scanner.go index 56c78aae81..552364f7f1 100644 --- a/pkg/cli/mcp_workflow_scanner.go +++ b/pkg/cli/mcp_workflow_scanner.go @@ -39,6 +39,9 @@ func ScanWorkflowsForMCP(workflowsDir string, serverFilter string, verbose bool) return nil, fmt.Errorf("failed to search for workflow files: %w", err) } + // Filter out README.md files + files = filterWorkflowFiles(files) + mcpWorkflowScannerLog.Printf("Found %d workflow files to scan", len(files)) var results []WorkflowMCPMetadata diff --git a/pkg/cli/remove_command.go b/pkg/cli/remove_command.go index 65d7649465..174a83f4b2 100644 --- a/pkg/cli/remove_command.go +++ b/pkg/cli/remove_command.go @@ -33,6 +33,9 @@ func RemoveWorkflows(pattern string, keepOrphans bool) error { return fmt.Errorf("failed to find workflow files: %w", err) } + // Filter out README.md files + mdFiles = filterWorkflowFiles(mdFiles) + removeLog.Printf("Found %d workflow files", len(mdFiles)) if len(mdFiles) == 0 { fmt.Println("No workflow files found to remove.") diff --git a/pkg/cli/status_command.go b/pkg/cli/status_command.go index 4850118513..6e148f265d 100644 --- a/pkg/cli/status_command.go +++ b/pkg/cli/status_command.go @@ -388,6 +388,9 @@ func getMarkdownWorkflowFiles(workflowDir string) ([]string, error) { return nil, fmt.Errorf("failed to find workflow files: %w", err) } + // Filter out README.md files + mdFiles = filterWorkflowFiles(mdFiles) + return mdFiles, nil } diff --git a/pkg/cli/workflows.go b/pkg/cli/workflows.go index ba29017e75..77a75c5e10 100644 --- a/pkg/cli/workflows.go +++ b/pkg/cli/workflows.go @@ -209,3 +209,21 @@ func suggestWorkflowNames(target string) []string { // Use the existing FindClosestMatches function from parser package return parser.FindClosestMatches(normalizedTarget, availableNames, 3) } + +// isWorkflowFile returns true if the file should be treated as a workflow file. +// README.md files are excluded as they are documentation, not workflows. +func isWorkflowFile(filename string) bool { + base := strings.ToLower(filepath.Base(filename)) + return base != "readme.md" +} + +// filterWorkflowFiles filters out non-workflow files from a list of markdown files. +func filterWorkflowFiles(files []string) []string { + var filtered []string + for _, file := range files { + if isWorkflowFile(file) { + filtered = append(filtered, file) + } + } + return filtered +} diff --git a/pkg/cli/workflows_test.go b/pkg/cli/workflows_test.go new file mode 100644 index 0000000000..3341167781 --- /dev/null +++ b/pkg/cli/workflows_test.go @@ -0,0 +1,235 @@ +package cli + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsWorkflowFile(t *testing.T) { + tests := []struct { + name string + filename string + expected bool + }{ + { + name: "regular workflow file", + filename: "my-workflow.md", + expected: true, + }, + { + name: "README.md should be excluded", + filename: "README.md", + expected: false, + }, + { + name: "readme.md lowercase should be excluded", + filename: "readme.md", + expected: false, + }, + { + name: "ReadMe.md mixed case should be excluded", + filename: "ReadMe.md", + expected: false, + }, + { + name: "READM.md with different name is included", + filename: "READM.md", + expected: true, + }, + { + name: "README-workflow.md with prefix is included", + filename: "README-workflow.md", + expected: true, + }, + { + name: "workflow-README.md with suffix is included", + filename: "workflow-README.md", + expected: true, + }, + { + name: "path with README.md at end should be excluded", + filename: ".github/workflows/README.md", + expected: false, + }, + { + name: "path with regular workflow should be included", + filename: ".github/workflows/my-workflow.md", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isWorkflowFile(tt.filename) + assert.Equal(t, tt.expected, result, "isWorkflowFile(%q) should return %v", tt.filename, tt.expected) + }) + } +} + +func TestFilterWorkflowFiles(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + name: "empty list", + input: []string{}, + expected: []string{}, + }, + { + name: "no README.md files", + input: []string{ + "workflow1.md", + "workflow2.md", + "my-test.md", + }, + expected: []string{ + "workflow1.md", + "workflow2.md", + "my-test.md", + }, + }, + { + name: "filter out README.md", + input: []string{ + "workflow1.md", + "README.md", + "workflow2.md", + }, + expected: []string{ + "workflow1.md", + "workflow2.md", + }, + }, + { + name: "filter out readme.md (lowercase)", + input: []string{ + "workflow1.md", + "readme.md", + "workflow2.md", + }, + expected: []string{ + "workflow1.md", + "workflow2.md", + }, + }, + { + name: "filter out ReadMe.md (mixed case)", + input: []string{ + "workflow1.md", + "ReadMe.md", + "workflow2.md", + }, + expected: []string{ + "workflow1.md", + "workflow2.md", + }, + }, + { + name: "keep README-prefixed files", + input: []string{ + "README-workflow.md", + "README.md", + "workflow-README.md", + }, + expected: []string{ + "README-workflow.md", + "workflow-README.md", + }, + }, + { + name: "filter with full paths", + input: []string{ + ".github/workflows/workflow1.md", + ".github/workflows/README.md", + ".github/workflows/workflow2.md", + }, + expected: []string{ + ".github/workflows/workflow1.md", + ".github/workflows/workflow2.md", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := filterWorkflowFiles(tt.input) + // Use ElementsMatch to handle nil vs empty slice differences + if len(tt.expected) == 0 && len(result) == 0 { + return // Both are empty, test passes + } + assert.Equal(t, tt.expected, result, "filterWorkflowFiles should filter correctly") + }) + } +} + +// TestGetMarkdownWorkflowFilesExcludesREADME tests that getMarkdownWorkflowFiles filters out README.md +func TestGetMarkdownWorkflowFilesExcludesREADME(t *testing.T) { + // Create a temporary directory structure + tempDir := t.TempDir() + workflowsDir := filepath.Join(tempDir, ".github", "workflows") + if err := os.MkdirAll(workflowsDir, 0755); err != nil { + t.Fatalf("Failed to create workflows directory: %v", err) + } + + // Create several workflow files + testFiles := map[string]string{ + "workflow1.md": "---\non: push\n---\n# Workflow 1", + "workflow2.md": "---\non: pull_request\n---\n# Workflow 2", + "README.md": "# This is a README", + "readme.md": "# This is a readme", + "ReadMe.md": "# This is a ReadMe", + "my-workflow.md": "---\non: workflow_dispatch\n---\n# My Workflow", + "README-test.md": "---\non: push\n---\n# README Test", + "test-README.md": "---\non: push\n---\n# Test README", + } + + for filename, content := range testFiles { + path := filepath.Join(workflowsDir, filename) + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + t.Fatalf("Failed to write test file %s: %v", filename, err) + } + } + + // Change to the temp directory + originalDir, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get current directory: %v", err) + } + defer os.Chdir(originalDir) + + if err := os.Chdir(tempDir); err != nil { + t.Fatalf("Failed to change directory: %v", err) + } + + // Get markdown workflow files + files, err := getMarkdownWorkflowFiles("") + if err != nil { + t.Fatalf("getMarkdownWorkflowFiles failed: %v", err) + } + + // Extract basenames for easier checking + var basenames []string + for _, file := range files { + basenames = append(basenames, filepath.Base(file)) + } + + // Should include regular workflow files and files with README in the middle + expectedFiles := []string{"workflow1.md", "workflow2.md", "my-workflow.md", "README-test.md", "test-README.md"} + for _, expected := range expectedFiles { + assert.Contains(t, basenames, expected, "Should include %s", expected) + } + + // Should NOT include any README.md variants (exact name, case-insensitive) + excludedFiles := []string{"README.md", "readme.md", "ReadMe.md"} + for _, excluded := range excludedFiles { + assert.NotContains(t, basenames, excluded, "Should NOT include %s", excluded) + } + + // Verify total count + assert.Equal(t, 5, len(files), "Should have exactly 5 workflow files (excluding README variants)") +} From 5aad96d96da0eb2ddd0bf0a19a86646a3bf8bbaa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 18:14:17 +0000 Subject: [PATCH 3/4] Format workflows_test.go --- pkg/cli/workflows_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/cli/workflows_test.go b/pkg/cli/workflows_test.go index 3341167781..256ca13a05 100644 --- a/pkg/cli/workflows_test.go +++ b/pkg/cli/workflows_test.go @@ -178,14 +178,14 @@ func TestGetMarkdownWorkflowFilesExcludesREADME(t *testing.T) { // Create several workflow files testFiles := map[string]string{ - "workflow1.md": "---\non: push\n---\n# Workflow 1", - "workflow2.md": "---\non: pull_request\n---\n# Workflow 2", - "README.md": "# This is a README", - "readme.md": "# This is a readme", - "ReadMe.md": "# This is a ReadMe", - "my-workflow.md": "---\non: workflow_dispatch\n---\n# My Workflow", - "README-test.md": "---\non: push\n---\n# README Test", - "test-README.md": "---\non: push\n---\n# Test README", + "workflow1.md": "---\non: push\n---\n# Workflow 1", + "workflow2.md": "---\non: pull_request\n---\n# Workflow 2", + "README.md": "# This is a README", + "readme.md": "# This is a readme", + "ReadMe.md": "# This is a ReadMe", + "my-workflow.md": "---\non: workflow_dispatch\n---\n# My Workflow", + "README-test.md": "---\non: push\n---\n# README Test", + "test-README.md": "---\non: push\n---\n# Test README", } for filename, content := range testFiles { From d02bfd052b0405e42b6f6830803204728693982b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 18:16:24 +0000 Subject: [PATCH 4/4] Fix linting issue - use assert.Len instead of assert.Equal for length checks Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/workflows_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/workflows_test.go b/pkg/cli/workflows_test.go index 256ca13a05..0fd1e4aaae 100644 --- a/pkg/cli/workflows_test.go +++ b/pkg/cli/workflows_test.go @@ -231,5 +231,5 @@ func TestGetMarkdownWorkflowFilesExcludesREADME(t *testing.T) { } // Verify total count - assert.Equal(t, 5, len(files), "Should have exactly 5 workflow files (excluding README variants)") + assert.Len(t, files, 5, "Should have exactly 5 workflow files (excluding README variants)") }