-
Notifications
You must be signed in to change notification settings - Fork 66
Display correct RHCOS version in Components for 4.Y and 5.Y releases #780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,6 +5,7 @@ import ( | |||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||
| "maps" | ||||||||||||||||||||||||||||||||||||
| "net/url" | ||||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||||
| "regexp" | ||||||||||||||||||||||||||||||||||||
| "slices" | ||||||||||||||||||||||||||||||||||||
| "sort" | ||||||||||||||||||||||||||||||||||||
|
|
@@ -42,8 +43,9 @@ var ( | |||||||||||||||||||||||||||||||||||
| reMdRHCoSVersion = regexp.MustCompile(`\* Red Hat Enterprise Linux CoreOS(?: \d+\.\d+)? ((\d+)\.[\w\.\-]+)\n`) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // RHEL 10 node image (rhel-coreos-10); match before generic RHCOS regex (longer prefix first). | ||||||||||||||||||||||||||||||||||||
| reMdRHCoS10Diff = regexp.MustCompile(`\* Red Hat Enterprise Linux CoreOS 10(?: \d+\.\d+)? upgraded from ((\d+)\.[\w\.\-]+) to ((\d+)\.[\w\.\-]+)\n`) | ||||||||||||||||||||||||||||||||||||
| reMdRHCoS10Version = regexp.MustCompile(`\* Red Hat Enterprise Linux CoreOS 10(?: \d+\.\d+)? ((\d+)\.[\w\.\-]+)\n`) | ||||||||||||||||||||||||||||||||||||
| // [. ] allows either "CoreOS 10.2 ..." (period) or "CoreOS 10 10.2 ..." (space) after "10". | ||||||||||||||||||||||||||||||||||||
| reMdRHCoS10Diff = regexp.MustCompile(`\* Red Hat Enterprise Linux CoreOS 10(?:[. ]\d[\d.]*)? upgraded from ((\d+)\.[\w\.\-]+) to ((\d+)\.[\w\.\-]+)\n`) | ||||||||||||||||||||||||||||||||||||
| reMdRHCoS10Version = regexp.MustCompile(`\* Red Hat Enterprise Linux CoreOS 10(?:[. ]\d[\d.]*)? ((\d+)\.[\w\.\-]+)\n`) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| reMdCentOSCoSDiff = regexp.MustCompile(`\* CentOS Stream CoreOS upgraded from ((\d+)\.[\w\.\-]+) to ((\d+)\.[\w\.\-]+)\n`) | ||||||||||||||||||||||||||||||||||||
| reMdCentOSCoSVersion = regexp.MustCompile(`\* CentOS Stream CoreOS ((\d+)\.[\w\.\-]+)\n`) | ||||||||||||||||||||||||||||||||||||
|
|
@@ -58,7 +60,161 @@ var ( | |||||||||||||||||||||||||||||||||||
| reRhelCoreOsVersion = regexp.MustCompile(`(\d+)\.(\d+)\.(\d+)-(\d+)`) | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| func TransformMarkDownOutput(markdown, fromTag, toTag, architecture, architectureExtension string) (string, error) { | ||||||||||||||||||||||||||||||||||||
| // swapRHCOSComponentIfNeeded replaces the RHCOS component shown in the ### Components section | ||||||||||||||||||||||||||||||||||||
| // with the preferred version based on the OpenShift major version (4.Y prefers RHCOS 9, 5.Y+ prefers RHCOS 10). | ||||||||||||||||||||||||||||||||||||
| func swapRHCOSComponentIfNeeded(markdown, toTag, architecture, architectureExtension string, releaseInfo releasecontroller.ReleaseInfo, toImage string) (string, error) { | ||||||||||||||||||||||||||||||||||||
| // Determine preferred machine-OS tag based on release version | ||||||||||||||||||||||||||||||||||||
| preferredTag := releasecontroller.PreferredMachineOSTag(toTag) | ||||||||||||||||||||||||||||||||||||
| if preferredTag == "" { | ||||||||||||||||||||||||||||||||||||
| // Can't parse version, skip swap | ||||||||||||||||||||||||||||||||||||
| return markdown, nil | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Query which machine-OS streams exist in the release | ||||||||||||||||||||||||||||||||||||
| streams, err := releaseInfo.ListMachineOSStreams(toImage) | ||||||||||||||||||||||||||||||||||||
| if err != nil || len(streams) == 0 { | ||||||||||||||||||||||||||||||||||||
| // Can't determine streams, skip swap | ||||||||||||||||||||||||||||||||||||
| return markdown, nil | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Check if both rhel-coreos and rhel-coreos-10 exist | ||||||||||||||||||||||||||||||||||||
| var hasRHCOS9, hasRHCOS10 bool | ||||||||||||||||||||||||||||||||||||
| var rhcos9Info, rhcos10Info releasecontroller.MachineOSStreamInfo | ||||||||||||||||||||||||||||||||||||
| for _, s := range streams { | ||||||||||||||||||||||||||||||||||||
| if s.Tag == "rhel-coreos" { | ||||||||||||||||||||||||||||||||||||
| hasRHCOS9 = true | ||||||||||||||||||||||||||||||||||||
| rhcos9Info = s | ||||||||||||||||||||||||||||||||||||
| } else if s.Tag == "rhel-coreos-10" { | ||||||||||||||||||||||||||||||||||||
| hasRHCOS10 = true | ||||||||||||||||||||||||||||||||||||
| rhcos10Info = s | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if !hasRHCOS9 || !hasRHCOS10 { | ||||||||||||||||||||||||||||||||||||
| // Only one RHCOS version exists, no need to swap | ||||||||||||||||||||||||||||||||||||
| return markdown, nil | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Parse the Components section to find which RHCOS is currently shown | ||||||||||||||||||||||||||||||||||||
| reComponentsSection := regexp.MustCompile(`(?s)(### Components.*?)\n\n###`) | ||||||||||||||||||||||||||||||||||||
| componentsMatch := reComponentsSection.FindStringSubmatch(markdown) | ||||||||||||||||||||||||||||||||||||
| if componentsMatch == nil { | ||||||||||||||||||||||||||||||||||||
| // Can't find Components section | ||||||||||||||||||||||||||||||||||||
| return markdown, nil | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| componentsSection := componentsMatch[1] | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Determine which RHCOS is currently shown and what we want to show | ||||||||||||||||||||||||||||||||||||
| var currentlyShown, wantToShow string | ||||||||||||||||||||||||||||||||||||
| var desiredInfo releasecontroller.MachineOSStreamInfo | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if strings.Contains(componentsSection, rhelCoreOs10) { | ||||||||||||||||||||||||||||||||||||
| currentlyShown = "rhel-coreos-10" | ||||||||||||||||||||||||||||||||||||
| } else if strings.Contains(componentsSection, rhelCoreOs) { | ||||||||||||||||||||||||||||||||||||
| currentlyShown = "rhel-coreos" | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| // No RHCOS component found in Components section | ||||||||||||||||||||||||||||||||||||
| return markdown, nil | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Determine what we want to show | ||||||||||||||||||||||||||||||||||||
| if preferredTag == "rhel-coreos-10" { | ||||||||||||||||||||||||||||||||||||
| wantToShow = "rhel-coreos-10" | ||||||||||||||||||||||||||||||||||||
| desiredInfo = rhcos10Info | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| wantToShow = "rhel-coreos" | ||||||||||||||||||||||||||||||||||||
| desiredInfo = rhcos9Info | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if currentlyShown == wantToShow { | ||||||||||||||||||||||||||||||||||||
| // Already showing the preferred version | ||||||||||||||||||||||||||||||||||||
| return markdown, nil | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Need to swap: fetch the version info for the desired RHCOS | ||||||||||||||||||||||||||||||||||||
| releaseJSON, err := releaseInfo.ReleaseInfo(toImage) | ||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||
| return markdown, fmt.Errorf("failed to get release info: %w", err) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Parse the release JSON to get the machine-os component version | ||||||||||||||||||||||||||||||||||||
| var relInfo struct { | ||||||||||||||||||||||||||||||||||||
| References struct { | ||||||||||||||||||||||||||||||||||||
| Spec struct { | ||||||||||||||||||||||||||||||||||||
| Tags []struct { | ||||||||||||||||||||||||||||||||||||
| Name string `json:"name"` | ||||||||||||||||||||||||||||||||||||
| Annotations map[string]string `json:"annotations"` | ||||||||||||||||||||||||||||||||||||
| } `json:"tags"` | ||||||||||||||||||||||||||||||||||||
| } `json:"spec"` | ||||||||||||||||||||||||||||||||||||
| } `json:"references"` | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if err := json.Unmarshal([]byte(releaseJSON), &relInfo); err != nil { | ||||||||||||||||||||||||||||||||||||
| return markdown, fmt.Errorf("failed to parse release JSON: %w", err) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Find the version annotation for the desired machine-OS tag | ||||||||||||||||||||||||||||||||||||
| var desiredVersion string | ||||||||||||||||||||||||||||||||||||
| for _, tag := range relInfo.References.Spec.Tags { | ||||||||||||||||||||||||||||||||||||
| if tag.Name == wantToShow { | ||||||||||||||||||||||||||||||||||||
| if versionAnnotation, ok := tag.Annotations["io.openshift.build.versions"]; ok { | ||||||||||||||||||||||||||||||||||||
| // Parse the version from the annotation, format: "machine-os=X.Y.Z" | ||||||||||||||||||||||||||||||||||||
| for _, part := range strings.Split(versionAnnotation, ",") { | ||||||||||||||||||||||||||||||||||||
| part = strings.TrimSpace(part) | ||||||||||||||||||||||||||||||||||||
| if strings.HasPrefix(part, "machine-os=") { | ||||||||||||||||||||||||||||||||||||
| desiredVersion = strings.TrimPrefix(part, "machine-os=") | ||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if desiredVersion == "" { | ||||||||||||||||||||||||||||||||||||
| // Couldn't find version, skip swap | ||||||||||||||||||||||||||||||||||||
| return markdown, nil | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Build the replacement RHCOS component line with proper formatting | ||||||||||||||||||||||||||||||||||||
| displayName := desiredInfo.DisplayName | ||||||||||||||||||||||||||||||||||||
| if displayName == "" { | ||||||||||||||||||||||||||||||||||||
| displayName = releasecontroller.MachineOSTitle(desiredInfo) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Build the URL to the RHCOS release browser | ||||||||||||||||||||||||||||||||||||
| stream, ok := getRHCoSReleaseStream(desiredVersion, architectureExtension) | ||||||||||||||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||||||||||||||
| // Can't determine stream, skip enrichment | ||||||||||||||||||||||||||||||||||||
| return markdown, nil | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| rhcosURL := url.URL{ | ||||||||||||||||||||||||||||||||||||
| Scheme: serviceScheme, | ||||||||||||||||||||||||||||||||||||
| Host: serviceUrl, | ||||||||||||||||||||||||||||||||||||
| Path: "/", | ||||||||||||||||||||||||||||||||||||
| Fragment: desiredVersion, | ||||||||||||||||||||||||||||||||||||
| RawQuery: (url.Values{ | ||||||||||||||||||||||||||||||||||||
| "stream": []string{stream}, | ||||||||||||||||||||||||||||||||||||
| "arch": []string{architecture}, | ||||||||||||||||||||||||||||||||||||
| "release": []string{desiredVersion}, | ||||||||||||||||||||||||||||||||||||
| }).Encode(), | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Create the new component line with enriched link and alert box | ||||||||||||||||||||||||||||||||||||
| enrichedComponent := fmt.Sprintf("* %s [%s](%s) %s", displayName, desiredVersion, rhcosURL.String(), baseLayerAlertBox) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Find and replace the old RHCOS component line in the Components section | ||||||||||||||||||||||||||||||||||||
| reComponentLine := regexp.MustCompile(`\* Red Hat Enterprise Linux CoreOS[^\n]+`) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| newComponentsSection := reComponentLine.ReplaceAllString(componentsSection, enrichedComponent) | ||||||||||||||||||||||||||||||||||||
| markdown = strings.Replace(markdown, componentsSection, newComponentsSection, 1) | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+209
to
+212
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace only the targeted RHCOS line, not every match. At Line 211, Suggested fix- reComponentLine := regexp.MustCompile(`\* Red Hat Enterprise Linux CoreOS[^\n]+`)
-
- newComponentsSection := reComponentLine.ReplaceAllString(componentsSection, enrichedComponent)
+ lines := strings.Split(componentsSection, "\n")
+ for i, line := range lines {
+ if !strings.HasPrefix(line, "* Red Hat Enterprise Linux CoreOS") {
+ continue
+ }
+ isRHCOS10 := strings.Contains(line, rhelCoreOs10)
+ if (currentlyShown == "rhel-coreos-10" && isRHCOS10) || (currentlyShown == "rhel-coreos" && !isRHCOS10) {
+ lines[i] = enrichedComponent
+ break
+ }
+ }
+ newComponentsSection := strings.Join(lines, "\n")
markdown = strings.Replace(markdown, componentsSection, newComponentsSection, 1)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| return markdown, nil | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| func TransformMarkDownOutput(markdown, fromTag, toTag, architecture, architectureExtension string, releaseInfo releasecontroller.ReleaseInfo, toImage string) (string, error) { | ||||||||||||||||||||||||||||||||||||
| // replace references to the previous version with links | ||||||||||||||||||||||||||||||||||||
| rePrevious, err := regexp.Compile(fmt.Sprintf(`([^\w:])%s(\W)`, regexp.QuoteMeta(fromTag))) | ||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||
|
|
@@ -75,6 +231,15 @@ func TransformMarkDownOutput(markdown, fromTag, toTag, architecture, architectur | |||||||||||||||||||||||||||||||||||
| // add link to tag from which current version promoted from | ||||||||||||||||||||||||||||||||||||
| markdown = reMdPromotedFrom.ReplaceAllString(markdown, fmt.Sprintf("Release %s was created from [$1:$2](/releasetag/$2)", toTag)) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Swap RHCOS component in Components section if needed (4.Y prefers RHCOS 9, 5.Y+ prefers RHCOS 10) | ||||||||||||||||||||||||||||||||||||
| if releaseInfo != nil && toImage != "" { | ||||||||||||||||||||||||||||||||||||
| markdown, err = swapRHCOSComponentIfNeeded(markdown, toTag, architecture, architectureExtension, releaseInfo, toImage) | ||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||
| // Log but don't fail - this is a best-effort improvement | ||||||||||||||||||||||||||||||||||||
| fmt.Fprintf(os.Stderr, "Warning: failed to swap RHCOS component: %v\n", err) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Apply CoreOS link transforms for every matching line (OpenShift 4.21+ may list RHCOS 9 and 10 separately). | ||||||||||||||||||||||||||||||||||||
| for { | ||||||||||||||||||||||||||||||||||||
| var m []string | ||||||||||||||||||||||||||||||||||||
|
|
@@ -85,7 +250,11 @@ func TransformMarkDownOutput(markdown, fromTag, toTag, architecture, architectur | |||||||||||||||||||||||||||||||||||
| name = rhelCoreOs10 | ||||||||||||||||||||||||||||||||||||
| case reMdRHCoSDiff.MatchString(markdown): | ||||||||||||||||||||||||||||||||||||
| m = reMdRHCoSDiff.FindStringSubmatch(markdown) | ||||||||||||||||||||||||||||||||||||
| name = rhelCoreOs | ||||||||||||||||||||||||||||||||||||
| if fromMajor, err := strconv.Atoi(m[2]); err == nil && fromMajor >= 10 && fromMajor < 100 { | ||||||||||||||||||||||||||||||||||||
| name = rhelCoreOs10 | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| name = rhelCoreOs | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| case reMdCentOSCoSDiff.MatchString(markdown): | ||||||||||||||||||||||||||||||||||||
| m = reMdCentOSCoSDiff.FindStringSubmatch(markdown) | ||||||||||||||||||||||||||||||||||||
| name = centosStreamCoreOs | ||||||||||||||||||||||||||||||||||||
|
|
@@ -106,7 +275,11 @@ func TransformMarkDownOutput(markdown, fromTag, toTag, architecture, architectur | |||||||||||||||||||||||||||||||||||
| name = rhelCoreOs10 | ||||||||||||||||||||||||||||||||||||
| case reMdRHCoSVersion.MatchString(markdown): | ||||||||||||||||||||||||||||||||||||
| m = reMdRHCoSVersion.FindStringSubmatch(markdown) | ||||||||||||||||||||||||||||||||||||
| name = rhelCoreOs | ||||||||||||||||||||||||||||||||||||
| if vMajor, err := strconv.Atoi(m[2]); err == nil && vMajor >= 10 && vMajor < 100 { | ||||||||||||||||||||||||||||||||||||
| name = rhelCoreOs10 | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| name = rhelCoreOs | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| case reMdCentOSCoSVersion.MatchString(markdown): | ||||||||||||||||||||||||||||||||||||
| m = reMdCentOSCoSVersion.FindStringSubmatch(markdown) | ||||||||||||||||||||||||||||||||||||
| name = centosStreamCoreOs | ||||||||||||||||||||||||||||||||||||
|
|
@@ -129,8 +302,10 @@ func TransformJsonOutput(output, architecture, architectureExtension string) (st | |||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| for i, component := range changeLogJson.Components { | ||||||||||||||||||||||||||||||||||||
| switch component.Name { | ||||||||||||||||||||||||||||||||||||
| case rhelCoreOs, rhelCoreOs10, centosStreamCoreOs: | ||||||||||||||||||||||||||||||||||||
| switch { | ||||||||||||||||||||||||||||||||||||
| case strings.HasPrefix(component.Name, rhelCoreOs10): | ||||||||||||||||||||||||||||||||||||
| changeLogJson.Components[i] = enrichCoreOSComponentJSON(component, architecture, architectureExtension) | ||||||||||||||||||||||||||||||||||||
| case component.Name == rhelCoreOs || component.Name == centosStreamCoreOs: | ||||||||||||||||||||||||||||||||||||
| changeLogJson.Components[i] = enrichCoreOSComponentJSON(component, architecture, architectureExtension) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle terminal Components sections in the matcher.
At Line 99, the regex requires a following
\n\n###, so swap is skipped when### Componentsis the last section in the markdown.Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents