diff --git a/pkg/cli/commands_utils_test.go b/pkg/cli/commands_utils_test.go index c469c055ba..1207b02a3d 100644 --- a/pkg/cli/commands_utils_test.go +++ b/pkg/cli/commands_utils_test.go @@ -113,6 +113,113 @@ func TestExtractWorkflowNameFromFile_NonExistentFile(t *testing.T) { } } +func TestFastParseTitle(t *testing.T) { + tests := []struct { + name string + content string + expected string + expectError bool + }{ + { + name: "H1 after frontmatter", + content: `--- +title: Test +--- + +# My Workflow Title + +Some content.`, + expected: "My Workflow Title", + }, + { + name: "H1 with trailing spaces", + content: `--- +engine: copilot +--- + +# Weekly Research + +Content here.`, + expected: "Weekly Research", + }, + { + name: "H1 without frontmatter", + content: `# Simple Title + +No frontmatter here.`, + expected: "Simple Title", + }, + { + name: "H1 is first line (no frontmatter)", + content: `# Inline Title`, + expected: "Inline Title", + }, + { + name: "no H1 header", + content: "Just some text without headers.", + expected: "", + }, + { + name: "only H2 headers", + content: `--- +engine: copilot +--- + +## Not an H1`, + expected: "", + }, + { + name: "empty content", + content: "", + expected: "", + }, + { + name: "unclosed frontmatter returns error", + content: `--- +title: Oops +`, + expectError: true, + }, + { + name: "dash in middle of content is not frontmatter", + content: `Some text + +--- + +# Not Skipped`, + expected: "Not Skipped", + }, + { + name: "H1 inside frontmatter is ignored", + content: `--- +# not a header +--- + +# Real Title`, + expected: "Real Title", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := fastParseTitle(tt.content) + if tt.expectError { + if err == nil { + t.Errorf("fastParseTitle(%q) expected error, got nil", tt.content) + } + return + } + if err != nil { + t.Errorf("fastParseTitle(%q) unexpected error: %v", tt.content, err) + return + } + if got != tt.expected { + t.Errorf("fastParseTitle(%q) = %q, want %q", tt.content, got, tt.expected) + } + }) + } +} + func TestIsGitRepo(t *testing.T) { // Test in current directory (should be a git repo based on project setup) result := isGitRepo() diff --git a/pkg/cli/workflows.go b/pkg/cli/workflows.go index 4cb268605e..e721eec2fc 100644 --- a/pkg/cli/workflows.go +++ b/pkg/cli/workflows.go @@ -282,6 +282,46 @@ func getMarkdownWorkflowFiles(workflowDir string) ([]string, error) { return mdFiles, nil } +// fastParseTitle scans markdown content for the first H1 header, skipping an +// optional frontmatter block, without performing a full YAML parse. +// +// Frontmatter is recognised only when "---" appears on the very first line +// (matching the behaviour of ExtractFrontmatterFromContent). Returns the H1 +// title text, or ("", nil) when no H1 header is present. Returns an error if +// frontmatter is opened but never closed. +func fastParseTitle(content string) (string, error) { + firstLine := true + inFrontmatter := false + pastFrontmatter := false + for line := range strings.SplitSeq(content, "\n") { + trimmed := strings.TrimSpace(line) + if firstLine { + firstLine = false + if trimmed == "---" { + inFrontmatter = true + continue + } + // No frontmatter on first line; treat the entire file as markdown. + pastFrontmatter = true + } else if inFrontmatter && !pastFrontmatter { + if trimmed == "---" { + pastFrontmatter = true + } + continue + } + if pastFrontmatter && strings.HasPrefix(trimmed, "# ") { + return strings.TrimSpace(trimmed[2:]), nil + } + } + + // Unclosed frontmatter is an error (consistent with ExtractFrontmatterFromContent). + if inFrontmatter && !pastFrontmatter { + return "", errors.New("frontmatter not properly closed") + } + + return "", nil +} + // extractWorkflowNameFromFile extracts the workflow name from a file's H1 header func extractWorkflowNameFromFile(filePath string) (string, error) { content, err := os.ReadFile(filePath) @@ -289,19 +329,12 @@ func extractWorkflowNameFromFile(filePath string) (string, error) { return "", err } - // Extract markdown content (excluding frontmatter) - result, err := parser.ExtractFrontmatterFromContent(string(content)) + title, err := fastParseTitle(string(content)) if err != nil { return "", err } - - // Look for first H1 header - lines := strings.SplitSeq(result.Markdown, "\n") - for line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "# ") { - return strings.TrimSpace(line[2:]), nil - } + if title != "" { + return title, nil } // No H1 header found, generate default name from filename