Refactor main.go to reduce cyclomatic complexity

Breaks down large functions into smaller, focused helpers to pass gocyclo linter:

Auto-import refactoring:
- Extract parseJSONLIssues() to handle JSONL parsing
- Extract handleCollisions() to detect and report conflicts
- Extract importIssueData() to coordinate issue/dep/label imports
- Extract updateExistingIssue() and createNewIssue() for clarity
- Extract importDependencies() and importLabels() for modularity

Flush refactoring:
- Extract recordFlushFailure() and recordFlushSuccess() for state management
- Extract readExistingJSONL() to isolate file reading logic
- Extract fetchDirtyIssuesFromDB() to separate DB access
- Extract writeIssuesToJSONL() to handle atomic writes

Command improvements:
- Extract executeLabelCommand() to eliminate duplication in label.go
- Extract addLabelsToIssue() helper for label management
- Replace deprecated strings.Title with manual capitalization

Configuration:
- Add gocyclo exception for test files in .golangci.yml

All tests passing, no functionality changes.
This commit is contained in:
Joshua Shanks
2025-10-15 21:06:17 -07:00
committed by Steve Yegge
parent cf4f11cff7
commit b1e8ef556e
6 changed files with 787 additions and 680 deletions

View File

@@ -183,6 +183,102 @@ func validateMarkdownPath(path string) (string, error) {
//
// ### Dependencies
// bd-10, bd-20
// markdownParseState holds state for parsing markdown files
type markdownParseState struct {
issues []*IssueTemplate
currentIssue *IssueTemplate
currentSection string
sectionContent strings.Builder
}
// finalizeSection processes and resets the current section
func (s *markdownParseState) finalizeSection() {
if s.currentIssue == nil || s.currentSection == "" {
return
}
content := s.sectionContent.String()
processIssueSection(s.currentIssue, s.currentSection, content)
s.sectionContent.Reset()
}
// handleH2Header handles H2 headers (new issue titles)
func (s *markdownParseState) handleH2Header(matches []string) {
// Finalize previous section if any
s.finalizeSection()
// Save previous issue if any
if s.currentIssue != nil {
s.issues = append(s.issues, s.currentIssue)
}
// Start new issue
s.currentIssue = &IssueTemplate{
Title: strings.TrimSpace(matches[1]),
Priority: 2, // Default priority
IssueType: "task", // Default type
}
s.currentSection = ""
}
// handleH3Header handles H3 headers (section titles)
func (s *markdownParseState) handleH3Header(matches []string) {
// Finalize previous section
s.finalizeSection()
// Start new section
s.currentSection = strings.TrimSpace(matches[1])
}
// handleContentLine handles regular content lines
func (s *markdownParseState) handleContentLine(line string) {
if s.currentIssue == nil {
return
}
// Content within a section
if s.currentSection != "" {
if s.sectionContent.Len() > 0 {
s.sectionContent.WriteString("\n")
}
s.sectionContent.WriteString(line)
return
}
// First lines after title (before any section) become description
if s.currentIssue.Description == "" && line != "" {
if s.currentIssue.Description != "" {
s.currentIssue.Description += "\n"
}
s.currentIssue.Description += line
}
}
// finalize completes parsing and returns the results
func (s *markdownParseState) finalize() ([]*IssueTemplate, error) {
// Finalize last section and issue
s.finalizeSection()
if s.currentIssue != nil {
s.issues = append(s.issues, s.currentIssue)
}
// Check if we found any issues
if len(s.issues) == 0 {
return nil, fmt.Errorf("no issues found in markdown file (expected ## Issue Title format)")
}
return s.issues, nil
}
// createMarkdownScanner creates a scanner with appropriate buffer size
func createMarkdownScanner(file *os.File) *bufio.Scanner {
scanner := bufio.NewScanner(file)
// Increase buffer size for large markdown files
const maxScannerBuffer = 1024 * 1024 // 1MB
buf := make([]byte, maxScannerBuffer)
scanner.Buffer(buf, maxScannerBuffer)
return scanner
}
func parseMarkdownFile(path string) ([]*IssueTemplate, error) {
// Validate and clean the file path
cleanPath, err := validateMarkdownPath(path)
@@ -199,91 +295,31 @@ func parseMarkdownFile(path string) ([]*IssueTemplate, error) {
_ = file.Close() // Close errors on read-only operations are not actionable
}()
var issues []*IssueTemplate
var currentIssue *IssueTemplate
var currentSection string
var sectionContent strings.Builder
scanner := bufio.NewScanner(file)
// Increase buffer size for large markdown files
const maxScannerBuffer = 1024 * 1024 // 1MB
buf := make([]byte, maxScannerBuffer)
scanner.Buffer(buf, maxScannerBuffer)
// Helper to finalize current section
finalizeSection := func() {
if currentIssue == nil || currentSection == "" {
return
}
content := sectionContent.String()
processIssueSection(currentIssue, currentSection, content)
sectionContent.Reset()
}
state := &markdownParseState{}
scanner := createMarkdownScanner(file)
for scanner.Scan() {
line := scanner.Text()
// Check for H2 (new issue)
if matches := h2Regex.FindStringSubmatch(line); matches != nil {
// Finalize previous section if any
finalizeSection()
// Save previous issue if any
if currentIssue != nil {
issues = append(issues, currentIssue)
}
// Start new issue
currentIssue = &IssueTemplate{
Title: strings.TrimSpace(matches[1]),
Priority: 2, // Default priority
IssueType: "task", // Default type
}
currentSection = ""
state.handleH2Header(matches)
continue
}
// Check for H3 (section within issue)
if matches := h3Regex.FindStringSubmatch(line); matches != nil {
// Finalize previous section
finalizeSection()
// Start new section
currentSection = strings.TrimSpace(matches[1])
state.handleH3Header(matches)
continue
}
// Regular content line - append to current section
if currentIssue != nil && currentSection != "" {
if sectionContent.Len() > 0 {
sectionContent.WriteString("\n")
}
sectionContent.WriteString(line)
} else if currentIssue != nil && currentSection == "" && currentIssue.Description == "" {
// First lines after title (before any section) become description
if line != "" {
if currentIssue.Description != "" {
currentIssue.Description += "\n"
}
currentIssue.Description += line
}
}
}
// Finalize last section and issue
finalizeSection()
if currentIssue != nil {
issues = append(issues, currentIssue)
// Regular content line
state.handleContentLine(line)
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error reading file: %w", err)
}
// Check if we found any issues
if len(issues) == 0 {
return nil, fmt.Errorf("no issues found in markdown file (expected ## Issue Title format)")
}
return issues, nil
return state.finalize()
}