package infra import ( "fmt" "log" "os" "path/filepath" "sort" "strings" ) // ScanIssuesDir escanea el directorio root (dev/issues/) y devuelve todos los Issues // encontrados en *.md directos y en completed/*.md. // Si un archivo falla al parsearse, se emite un warning al log y se continua. // Los issues se devuelven ordenados por ID ascendente. func ScanIssuesDir(root string) ([]Issue, error) { // Verificar que el directorio raiz existe. if _, err := os.Stat(root); err != nil { return nil, fmt.Errorf("scan_issues_dir: root dir %s: %w", root, err) } var issues []Issue // Patterns a escanear: archivos directos y completed/ patterns := []string{ filepath.Join(root, "*.md"), filepath.Join(root, "completed", "*.md"), } for _, pattern := range patterns { matches, err := filepath.Glob(pattern) if err != nil { return nil, fmt.Errorf("scan_issues_dir: glob %s: %w", pattern, err) } for _, path := range matches { // Saltar INDEX.md y README.md base := filepath.Base(path) if strings.EqualFold(base, "INDEX.md") || strings.EqualFold(base, "README.md") { continue } // Verificar que es un archivo regular info, err := os.Stat(path) if err != nil || !info.Mode().IsRegular() { continue } iss, _, err := ParseIssueMd(path) if err != nil { log.Printf("scan_issues_dir: warning: skip %s: %v", path, err) continue } issues = append(issues, iss) } } sort.Slice(issues, func(i, j int) bool { return issues[i].ID < issues[j].ID }) return issues, nil }