From 17d78a60bcc6288ee106025ffdd5e71242f05e3f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 00:08:51 +0000 Subject: [PATCH 1/9] Initial plan From 809fb585bd272f3aa99cd7d1b6a2034efed6994a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 00:22:39 +0000 Subject: [PATCH 2/9] Work in progress on applies_to in LLM markdown output Co-authored-by: reakaleek <16325797+reakaleek@users.noreply.github.com> --- .../Exporters/LlmMarkdownExporter.cs | 9 ++ .../LlmMarkdown/LlmAppliesToHelper.cs | 94 +++++++++++++++++++ .../LlmMarkdown/LlmBlockRenderers.cs | 13 ++- .../LlmMarkdown/LlmInlineRenderers.cs | 9 +- .../LlmMarkdown/LlmMarkdownOutput.fs | 4 +- 5 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs diff --git a/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs b/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs index 09e78ddad..6d8675402 100644 --- a/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs +++ b/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs @@ -8,6 +8,7 @@ using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Products; using Elastic.Markdown.Helpers; +using Elastic.Markdown.Myst.Renderers.LlmMarkdown; using Markdig.Syntax; namespace Elastic.Markdown.Exporters; @@ -155,6 +156,14 @@ private string CreateLlmContentWithMetadata(MarkdownExportFileContext context, s _ = metadata.AppendLine($" - {item}"); } + // Add applies_to information from frontmatter + if (sourceFile.YamlFrontMatter?.AppliesTo is not null) + { + var appliesToText = LlmAppliesToHelper.RenderApplicableTo(sourceFile.YamlFrontMatter.AppliesTo, context.BuildContext); + if (!string.IsNullOrEmpty(appliesToText)) + _ = metadata.AppendLine($"applies_to: {appliesToText}"); + } + _ = metadata.AppendLine("---"); _ = metadata.AppendLine(); _ = metadata.AppendLine($"# {sourceFile.Title}"); diff --git a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs new file mode 100644 index 000000000..23b1d6ee4 --- /dev/null +++ b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs @@ -0,0 +1,94 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Text; +using Elastic.Documentation; +using Elastic.Documentation.AppliesTo; +using Elastic.Documentation.Configuration; +using Elastic.Markdown.Myst.Components; + +namespace Elastic.Markdown.Myst.Renderers.LlmMarkdown; + +/// +/// Helper class to render ApplicableTo information in LLM-friendly text format +/// +public static class LlmAppliesToHelper +{ + /// + /// Converts ApplicableTo to a readable text format for LLM consumption + /// + public static string RenderApplicableTo(ApplicableTo? appliesTo, IDocumentationConfigurationContext buildContext) + { + if (appliesTo is null || appliesTo == ApplicableTo.All) + return string.Empty; + + var viewModel = new ApplicableToViewModel + { + AppliesTo = appliesTo, + Inline = false, + ShowTooltip = false, + VersionsConfig = buildContext.VersionsConfiguration + }; + + var items = viewModel.GetApplicabilityItems(); + if (items.Count == 0) + return string.Empty; + + var itemList = new List(); + + foreach (var item in items) + { + var text = BuildApplicabilityText(item); + if (!string.IsNullOrEmpty(text)) + itemList.Add(text); + } + + if (itemList.Count == 0) + return string.Empty; + + return string.Join(", ", itemList); + } + + private static string BuildApplicabilityText(ApplicabilityItem item) + { + // For LLM output, use the shorter Key name for better readability + var parts = new List { item.Key }; + + // For LLM output, show the actual applicability information directly + var applicability = item.Applicability; + + // Add lifecycle if it's not GA + if (applicability.Lifecycle != ProductLifecycle.GenerallyAvailable) + parts.Add(applicability.GetLifeCycleName()); + + // Add version information if present + if (applicability.Version is not null and not AllVersionsSpec) + { + var versionText = FormatVersion(applicability.Version); + if (!string.IsNullOrEmpty(versionText)) + parts.Add(versionText); + } + + return string.Join(" ", parts); + } + + private static string FormatVersion(VersionSpec versionSpec) + { + var min = versionSpec.Min; + var max = versionSpec.Max; + var showMinPatch = versionSpec.ShowMinPatch; + var showMaxPatch = versionSpec.ShowMaxPatch; + + static string FormatSemVersion(SemVersion v, bool showPatch) => + showPatch ? $"{v.Major}.{v.Minor}.{v.Patch}" : $"{v.Major}.{v.Minor}"; + + return versionSpec.Kind switch + { + VersionSpecKind.GreaterThanOrEqual => $"{FormatSemVersion(min, showMinPatch)}+", + VersionSpecKind.Range when max is not null => $"{FormatSemVersion(min, showMinPatch)}-{FormatSemVersion(max, showMaxPatch)}", + VersionSpecKind.Exact => FormatSemVersion(min, showMinPatch), + _ => string.Empty + }; + } +} diff --git a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs index d5eeec1c9..35b32f0f0 100644 --- a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs +++ b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs @@ -436,7 +436,18 @@ protected override void Write(LlmMarkdownRenderer renderer, DirectiveBlock obj) switch (obj) { case IBlockAppliesTo appliesBlock when !string.IsNullOrEmpty(appliesBlock.AppliesToDefinition): - renderer.Writer.Write($" applies-to=\"{appliesBlock.AppliesToDefinition}\""); + // Check if the block has a parsed AppliesTo object (e.g., AdmonitionBlock) + var appliesToText = obj switch + { + AdmonitionBlock admonition when admonition.AppliesTo is not null => + LlmAppliesToHelper.RenderApplicableTo(admonition.AppliesTo, renderer.BuildContext), + _ => null + }; + // Fallback to raw definition if parsing didn't work or returned empty + appliesToText ??= appliesBlock.AppliesToDefinition; + + if (!string.IsNullOrEmpty(appliesToText)) + renderer.Writer.Write($" applies-to=\"{appliesToText}\""); break; } diff --git a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmInlineRenderers.cs b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmInlineRenderers.cs index a78bcd534..5be49bf2b 100644 --- a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmInlineRenderers.cs +++ b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmInlineRenderers.cs @@ -4,6 +4,7 @@ using Elastic.Markdown.Myst.InlineParsers.Substitution; using Elastic.Markdown.Myst.Roles; +using Elastic.Markdown.Myst.Roles.AppliesTo; using Elastic.Markdown.Myst.Roles.Kbd; using Markdig.Renderers; using Markdig.Syntax.Inlines; @@ -101,7 +102,13 @@ protected override void Write(LlmMarkdownRenderer renderer, RoleLeaf obj) renderer.Writer.Write(output); break; } - // TODO: Add support for applies_to role + case AppliesToRole appliesTo: + { + var text = LlmAppliesToHelper.RenderApplicableTo(appliesTo.AppliesTo, renderer.BuildContext); + if (!string.IsNullOrEmpty(text)) + renderer.Writer.Write($"[{text}]"); + break; + } default: { new LlmCodeInlineRenderer().Write(renderer, obj); diff --git a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs index 123455e19..2abc73147 100644 --- a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs +++ b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs @@ -133,9 +133,9 @@ This is an inline {applies_to}`stack: preview 9.1` element. """ [] - let ``converts to plain text with optional comment`` () = + let ``converts to readable text`` () = markdown |> convertsToNewLLM """ - This is an inline `stack: preview 9.1` element. + This is an inline [Stack Preview 9.1+] element. """ type ``admonition directive`` () = From c2b91808eab99cad48966aab681af79d6730c33d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 00:28:54 +0000 Subject: [PATCH 3/9] Implement LLM-friendly applies_to rendering using popover data Co-authored-by: reakaleek <16325797+reakaleek@users.noreply.github.com> --- .../Exporters/LlmMarkdownExporter.cs | 4 +- .../LlmMarkdown/LlmAppliesToHelper.cs | 108 ++++++++++-------- .../LlmMarkdown/LlmBlockRenderers.cs | 2 +- .../LlmMarkdown/LlmMarkdownOutput.fs | 2 +- 4 files changed, 65 insertions(+), 51 deletions(-) diff --git a/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs b/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs index 6d8675402..d5f2371e2 100644 --- a/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs +++ b/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs @@ -159,9 +159,9 @@ private string CreateLlmContentWithMetadata(MarkdownExportFileContext context, s // Add applies_to information from frontmatter if (sourceFile.YamlFrontMatter?.AppliesTo is not null) { - var appliesToText = LlmAppliesToHelper.RenderApplicableTo(sourceFile.YamlFrontMatter.AppliesTo, context.BuildContext); + var appliesToText = LlmAppliesToHelper.RenderAppliesToBlock(sourceFile.YamlFrontMatter.AppliesTo, context.BuildContext); if (!string.IsNullOrEmpty(appliesToText)) - _ = metadata.AppendLine($"applies_to: {appliesToText}"); + _ = metadata.Append(appliesToText); } _ = metadata.AppendLine("---"); diff --git a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs index 23b1d6ee4..c55a0ff01 100644 --- a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs +++ b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs @@ -16,13 +16,47 @@ namespace Elastic.Markdown.Myst.Renderers.LlmMarkdown; public static class LlmAppliesToHelper { /// - /// Converts ApplicableTo to a readable text format for LLM consumption + /// Converts ApplicableTo to a readable text format for LLM consumption (block level - for page or section) + /// + public static string RenderAppliesToBlock(ApplicableTo? appliesTo, IDocumentationConfigurationContext buildContext) + { + if (appliesTo is null || appliesTo == ApplicableTo.All) + return string.Empty; + + var items = GetApplicabilityItems(appliesTo, buildContext); + if (items.Count == 0) + return string.Empty; + + var sb = new StringBuilder(); + _ = sb.AppendLine(); + _ = sb.AppendLine("This applies to:"); + + foreach (var (productName, availabilityText) in items) + _ = sb.AppendLine($"- {availabilityText} for {productName}"); + + return sb.ToString(); + } + + /// + /// Converts ApplicableTo to a readable inline text format for LLM consumption /// public static string RenderApplicableTo(ApplicableTo? appliesTo, IDocumentationConfigurationContext buildContext) { if (appliesTo is null || appliesTo == ApplicableTo.All) return string.Empty; + var items = GetApplicabilityItems(appliesTo, buildContext); + if (items.Count == 0) + return string.Empty; + + var itemList = items.Select(item => $"{item.availabilityText} for {item.productName}").ToList(); + return string.Join(", ", itemList); + } + + private static List<(string productName, string availabilityText)> GetApplicabilityItems( + ApplicableTo appliesTo, + IDocumentationConfigurationContext buildContext) + { var viewModel = new ApplicableToViewModel { AppliesTo = appliesTo, @@ -31,64 +65,44 @@ public static string RenderApplicableTo(ApplicableTo? appliesTo, IDocumentationC VersionsConfig = buildContext.VersionsConfiguration }; - var items = viewModel.GetApplicabilityItems(); - if (items.Count == 0) - return string.Empty; + var applicabilityItems = viewModel.GetApplicabilityItems(); + var results = new List<(string productName, string availabilityText)>(); - var itemList = new List(); - - foreach (var item in items) + foreach (var item in applicabilityItems) { - var text = BuildApplicabilityText(item); - if (!string.IsNullOrEmpty(text)) - itemList.Add(text); - } + var renderData = item.RenderData; + var productName = item.Key; - if (itemList.Count == 0) - return string.Empty; + // Get the availability text from the popover data + var availabilityText = GetAvailabilityText(renderData); + if (!string.IsNullOrEmpty(availabilityText)) + results.Add((productName, availabilityText)); + } - return string.Join(", ", itemList); + return results; } - private static string BuildApplicabilityText(ApplicabilityItem item) + private static string GetAvailabilityText(ApplicabilityRenderer.ApplicabilityRenderData renderData) { - // For LLM output, use the shorter Key name for better readability - var parts = new List { item.Key }; - - // For LLM output, show the actual applicability information directly - var applicability = item.Applicability; - - // Add lifecycle if it's not GA - if (applicability.Lifecycle != ProductLifecycle.GenerallyAvailable) - parts.Add(applicability.GetLifeCycleName()); - - // Add version information if present - if (applicability.Version is not null and not AllVersionsSpec) + // Use the first availability item's text if available (this is what the popover shows) + if (renderData.PopoverData?.AvailabilityItems is { Length: > 0 } items) { - var versionText = FormatVersion(applicability.Version); - if (!string.IsNullOrEmpty(versionText)) - parts.Add(versionText); + // The popover text already includes lifecycle and version info + // e.g., "Generally available since 9.1", "Preview in 8.0", etc. + return items[0].Text; } - return string.Join(" ", parts); - } + // Fallback to constructing from badge data + var parts = new List(); - private static string FormatVersion(VersionSpec versionSpec) - { - var min = versionSpec.Min; - var max = versionSpec.Max; - var showMinPatch = versionSpec.ShowMinPatch; - var showMaxPatch = versionSpec.ShowMaxPatch; + if (!string.IsNullOrEmpty(renderData.LifecycleName) && renderData.LifecycleName != "Generally available") + parts.Add(renderData.LifecycleName); - static string FormatSemVersion(SemVersion v, bool showPatch) => - showPatch ? $"{v.Major}.{v.Minor}.{v.Patch}" : $"{v.Major}.{v.Minor}"; + if (!string.IsNullOrEmpty(renderData.Version)) + parts.Add(renderData.Version); + else if (!string.IsNullOrEmpty(renderData.BadgeLifecycleText)) + parts.Add(renderData.BadgeLifecycleText); - return versionSpec.Kind switch - { - VersionSpecKind.GreaterThanOrEqual => $"{FormatSemVersion(min, showMinPatch)}+", - VersionSpecKind.Range when max is not null => $"{FormatSemVersion(min, showMinPatch)}-{FormatSemVersion(max, showMaxPatch)}", - VersionSpecKind.Exact => FormatSemVersion(min, showMinPatch), - _ => string.Empty - }; + return parts.Count > 0 ? string.Join(" ", parts) : "Available"; } } diff --git a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs index 35b32f0f0..26a6e5b74 100644 --- a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs +++ b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs @@ -445,7 +445,7 @@ protected override void Write(LlmMarkdownRenderer renderer, DirectiveBlock obj) }; // Fallback to raw definition if parsing didn't work or returned empty appliesToText ??= appliesBlock.AppliesToDefinition; - + if (!string.IsNullOrEmpty(appliesToText)) renderer.Writer.Write($" applies-to=\"{appliesToText}\""); break; diff --git a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs index 2abc73147..3b9ac5b2d 100644 --- a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs +++ b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs @@ -135,7 +135,7 @@ This is an inline {applies_to}`stack: preview 9.1` element. [] let ``converts to readable text`` () = markdown |> convertsToNewLLM """ - This is an inline [Stack Preview 9.1+] element. + This is an inline [Planned in Stack] element. """ type ``admonition directive`` () = From 1c2a134dc023bc88000aa4af8c21612cbca507e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 00:34:20 +0000 Subject: [PATCH 4/9] Update test expectations for LLM applies_to rendering Co-authored-by: reakaleek <16325797+reakaleek@users.noreply.github.com> --- tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs index 3b9ac5b2d..1d57025cf 100644 --- a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs +++ b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs @@ -135,7 +135,7 @@ This is an inline {applies_to}`stack: preview 9.1` element. [] let ``converts to readable text`` () = markdown |> convertsToNewLLM """ - This is an inline [Planned in Stack] element. + This is an inline [Planned for Stack] element. """ type ``admonition directive`` () = @@ -214,15 +214,15 @@ This is a custom admonition with applies_to information. [] let ``renders correctly with applies_to information`` () = markdown |> convertsToNewLLM """ - + This is a note admonition with applies_to information. - + This is a warning admonition with applies_to information. - + This is a tip admonition with applies_to information. From fcbf94bc84327aed4fbf8e49101c7dbf47e961a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 00:37:19 +0000 Subject: [PATCH 5/9] Address code review comments with clarifying documentation Co-authored-by: reakaleek <16325797+reakaleek@users.noreply.github.com> --- .../Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs | 2 ++ .../Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs index c55a0ff01..58568bd19 100644 --- a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs +++ b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmAppliesToHelper.cs @@ -89,6 +89,8 @@ private static string GetAvailabilityText(ApplicabilityRenderer.ApplicabilityRen { // The popover text already includes lifecycle and version info // e.g., "Generally available since 9.1", "Preview in 8.0", etc. + // We use the first item because it represents the most current/relevant status + // (items are sorted by version descending in ApplicabilityRenderer) return items[0].Text; } diff --git a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs index 26a6e5b74..5b1e44d3f 100644 --- a/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs +++ b/src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs @@ -437,6 +437,8 @@ protected override void Write(LlmMarkdownRenderer renderer, DirectiveBlock obj) { case IBlockAppliesTo appliesBlock when !string.IsNullOrEmpty(appliesBlock.AppliesToDefinition): // Check if the block has a parsed AppliesTo object (e.g., AdmonitionBlock) + // Only AdmonitionBlock currently parses the YAML into an ApplicableTo object + // Other directive types may implement IBlockAppliesTo but not parse it var appliesToText = obj switch { AdmonitionBlock admonition when admonition.AppliesTo is not null => From 0c1252ff2d1319ec0fe76e4a2df95313e8f90545 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 00:46:38 +0000 Subject: [PATCH 6/9] Add test for page-level applies_to in frontmatter Co-authored-by: reakaleek <16325797+reakaleek@users.noreply.github.com> --- .../LlmMarkdown/LlmMarkdownOutput.fs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs index 1d57025cf..15889331e 100644 --- a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs +++ b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs @@ -751,3 +751,38 @@ type ``images in tables`` () = |---------------------------------------|------| | ![logo](https://example.com/logo.png) | Logo | """ + +type ``page level applies_to in frontmatter`` () = + static let markdown = Setup.Document """--- +applies_to: + stack: ga 8.5 + serverless: preview +--- + +# Test Page + +This is a test page with applies_to frontmatter. +""" + + [] + let ``parses applies_to frontmatter correctly`` () = + // Test that the applies_to frontmatter is correctly parsed + let results = markdown.Value + let defaultFile = results.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") + + // Test that the file has the correct applies_to information + test <@ defaultFile.File.YamlFrontMatter <> null @> + match defaultFile.File.YamlFrontMatter with + | NonNull yamlFrontMatter -> + test <@ yamlFrontMatter.AppliesTo <> null @> + match yamlFrontMatter.AppliesTo with + | NonNull appliesTo -> + // Verify stack applies_to + match appliesTo.Stack with + | NonNull stack -> test <@ stack.Count > 0 @> + | _ -> () + + // Verify serverless applies_to + test <@ appliesTo.Serverless <> null @> + | _ -> () + | _ -> () From e8369f50f46773e3937619acb7b3fbc80822c299 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 11:53:11 +0000 Subject: [PATCH 7/9] Improve page-level applies_to test with clearer assertions Co-authored-by: reakaleek <16325797+reakaleek@users.noreply.github.com> --- .../LlmMarkdown/LlmMarkdownOutput.fs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs index 15889331e..466000ba1 100644 --- a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs +++ b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs @@ -765,24 +765,24 @@ This is a test page with applies_to frontmatter. """ [] - let ``parses applies_to frontmatter correctly`` () = - // Test that the applies_to frontmatter is correctly parsed + let ``outputs content correctly`` () = + // The page content should be rendered in LLM markdown + markdown |> convertsToNewLLM """ +This is a test page with applies_to frontmatter. +""" + + [] + let ``parses applies_to from frontmatter`` () = + // Verify that applies_to frontmatter is parsed into the ApplicableTo object + // Note: The actual "This applies to:" text is added during export in LlmMarkdownExporter.CreateLlmContentWithMetadata + // which is not tested by convertsToNewLLM (that only tests document content, not the full export metadata) let results = markdown.Value let defaultFile = results.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") - // Test that the file has the correct applies_to information + // Verify frontmatter was parsed test <@ defaultFile.File.YamlFrontMatter <> null @> match defaultFile.File.YamlFrontMatter with | NonNull yamlFrontMatter -> + // Verify applies_to was parsed test <@ yamlFrontMatter.AppliesTo <> null @> - match yamlFrontMatter.AppliesTo with - | NonNull appliesTo -> - // Verify stack applies_to - match appliesTo.Stack with - | NonNull stack -> test <@ stack.Count > 0 @> - | _ -> () - - // Verify serverless applies_to - test <@ appliesTo.Serverless <> null @> - | _ -> () | _ -> () From b3a3094d341147148f692195854cf9f29993c1c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 11:57:46 +0000 Subject: [PATCH 8/9] Add clearer documentation for page-level applies_to test with expected export format Co-authored-by: reakaleek <16325797+reakaleek@users.noreply.github.com> --- .../Framework/LlmMarkdownAssertions.fs | 18 ++++++++++++ .../LlmMarkdown/LlmMarkdownOutput.fs | 29 +++++++++++++++---- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/tests/authoring/Framework/LlmMarkdownAssertions.fs b/tests/authoring/Framework/LlmMarkdownAssertions.fs index 91c6404ca..ffbd9ce6a 100644 --- a/tests/authoring/Framework/LlmMarkdownAssertions.fs +++ b/tests/authoring/Framework/LlmMarkdownAssertions.fs @@ -37,5 +37,23 @@ module LlmMarkdownAssertions = -- ACTUAL -- {actualLLM} +""" + raise (XunitException(msg)) + + [] + let llmOutputContains (expectedText: string) (actual: Lazy) = + // Check if the document content contains the expected text + let results = actual.Value + let defaultFile = results.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") + let actualLLM = toLlmMarkdown defaultFile + + if not (actualLLM.Contains(expectedText)) then + let msg = $"""LLM output does not contain expected text + +-- EXPECTED TO CONTAIN -- +{expectedText} + +-- ACTUAL OUTPUT -- +{actualLLM} """ raise (XunitException(msg)) diff --git a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs index 466000ba1..602782750 100644 --- a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs +++ b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs @@ -765,17 +765,27 @@ This is a test page with applies_to frontmatter. """ [] - let ``outputs content correctly`` () = + let ``renders content correctly`` () = // The page content should be rendered in LLM markdown markdown |> convertsToNewLLM """ This is a test page with applies_to frontmatter. """ [] - let ``parses applies_to from frontmatter`` () = - // Verify that applies_to frontmatter is parsed into the ApplicableTo object - // Note: The actual "This applies to:" text is added during export in LlmMarkdownExporter.CreateLlmContentWithMetadata - // which is not tested by convertsToNewLLM (that only tests document content, not the full export metadata) + let ``frontmatter applies_to is exported in metadata section`` () = + // The applies_to from frontmatter is processed and would appear in the exported LLM file + // In the format: + // --- + // title: Test Page + // description: ... + // + // This applies to: + // - Generally available since 8.5 for Stack + // - Preview for Serverless + // --- + // + // This happens in LlmMarkdownExporter.CreateLlmContentWithMetadata during export. + // We verify the frontmatter is parsed correctly so it can be used during export. let results = markdown.Value let defaultFile = results.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") @@ -783,6 +793,13 @@ This is a test page with applies_to frontmatter. test <@ defaultFile.File.YamlFrontMatter <> null @> match defaultFile.File.YamlFrontMatter with | NonNull yamlFrontMatter -> - // Verify applies_to was parsed + // Verify applies_to was parsed and contains expected data test <@ yamlFrontMatter.AppliesTo <> null @> + match yamlFrontMatter.AppliesTo with + | NonNull appliesTo -> + // Verify Stack configuration exists + test <@ appliesTo.Stack <> null @> + // Verify Serverless configuration exists + test <@ appliesTo.Serverless <> null @> + | _ -> () | _ -> () From 5a4c5608eca5d463cda8589daafa0d1d7235dbbf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 00:14:02 +0000 Subject: [PATCH 9/9] Add public API to test LLM export metadata and test applies_to rendering Co-authored-by: reakaleek <16325797+reakaleek@users.noreply.github.com> --- .../Exporters/LlmMarkdownExporter.cs | 11 ++++- .../Framework/LlmMarkdownAssertions.fs | 18 -------- .../LlmMarkdown/LlmMarkdownOutput.fs | 44 +++++++------------ 3 files changed, 24 insertions(+), 49 deletions(-) diff --git a/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs b/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs index d5f2371e2..109b5f9e4 100644 --- a/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs +++ b/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs @@ -77,7 +77,7 @@ public async ValueTask ExportAsync(MarkdownExportFileContext fileContext, if (outputFile.Directory is { Exists: false }) outputFile.Directory.Create(); - var content = IsRootIndexFile(fileContext) ? LlmsTxtTemplate : CreateLlmContentWithMetadata(fileContext, llmMarkdown); + var content = IsRootIndexFile(fileContext) ? LlmsTxtTemplate : CreateLlmContentWithMetadataInternal(fileContext, llmMarkdown); await fileContext.SourceFile.SourceFile.FileSystem.File.WriteAllTextAsync( outputFile.FullName, @@ -94,6 +94,13 @@ public static string ConvertToLlmMarkdown(MarkdownDocument document, IDocumentat _ = renderer.Render(obj); }); + /// + /// Creates the full LLM content with metadata section (frontmatter). + /// This is exposed for testing purposes. + /// + public static string CreateLlmContentWithMetadata(MarkdownExportFileContext context, string llmMarkdown) => + new LlmMarkdownExporter().CreateLlmContentWithMetadataInternal(context, llmMarkdown); + private static bool IsRootIndexFile(MarkdownExportFileContext fileContext) { var fs = fileContext.BuildContext.ReadFileSystem; @@ -129,7 +136,7 @@ private static IFileInfo GetLlmOutputFile(MarkdownExportFileContext fileContext) } - private string CreateLlmContentWithMetadata(MarkdownExportFileContext context, string llmMarkdown) + private string CreateLlmContentWithMetadataInternal(MarkdownExportFileContext context, string llmMarkdown) { var sourceFile = context.SourceFile; var metadata = DocumentationObjectPoolProvider.StringBuilderPool.Get(); diff --git a/tests/authoring/Framework/LlmMarkdownAssertions.fs b/tests/authoring/Framework/LlmMarkdownAssertions.fs index ffbd9ce6a..91c6404ca 100644 --- a/tests/authoring/Framework/LlmMarkdownAssertions.fs +++ b/tests/authoring/Framework/LlmMarkdownAssertions.fs @@ -37,23 +37,5 @@ module LlmMarkdownAssertions = -- ACTUAL -- {actualLLM} -""" - raise (XunitException(msg)) - - [] - let llmOutputContains (expectedText: string) (actual: Lazy) = - // Check if the document content contains the expected text - let results = actual.Value - let defaultFile = results.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") - let actualLLM = toLlmMarkdown defaultFile - - if not (actualLLM.Contains(expectedText)) then - let msg = $"""LLM output does not contain expected text - --- EXPECTED TO CONTAIN -- -{expectedText} - --- ACTUAL OUTPUT -- -{actualLLM} """ raise (XunitException(msg)) diff --git a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs index 602782750..b99b3d2ba 100644 --- a/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs +++ b/tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs @@ -765,41 +765,27 @@ This is a test page with applies_to frontmatter. """ [] - let ``renders content correctly`` () = - // The page content should be rendered in LLM markdown - markdown |> convertsToNewLLM """ -This is a test page with applies_to frontmatter. -""" - - [] - let ``frontmatter applies_to is exported in metadata section`` () = - // The applies_to from frontmatter is processed and would appear in the exported LLM file - // In the format: - // --- - // title: Test Page - // description: ... - // - // This applies to: - // - Generally available since 8.5 for Stack - // - Preview for Serverless - // --- - // - // This happens in LlmMarkdownExporter.CreateLlmContentWithMetadata during export. - // We verify the frontmatter is parsed correctly so it can be used during export. + let ``exports with applies_to in metadata`` () = + // Test that the applies_to helper renders the expected output let results = markdown.Value let defaultFile = results.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") - // Verify frontmatter was parsed + // Get the AppliesTo object from frontmatter test <@ defaultFile.File.YamlFrontMatter <> null @> match defaultFile.File.YamlFrontMatter with | NonNull yamlFrontMatter -> - // Verify applies_to was parsed and contains expected data test <@ yamlFrontMatter.AppliesTo <> null @> match yamlFrontMatter.AppliesTo with | NonNull appliesTo -> - // Verify Stack configuration exists - test <@ appliesTo.Stack <> null @> - // Verify Serverless configuration exists - test <@ appliesTo.Serverless <> null @> - | _ -> () - | _ -> () + // Test that the LlmAppliesToHelper renders the correct output + let appliesToText = Elastic.Markdown.Myst.Renderers.LlmMarkdown.LlmAppliesToHelper.RenderAppliesToBlock( + appliesTo, + defaultFile.Context.Generator.Context + ) + + // Verify it contains the expected sections + test <@ appliesToText.Contains("This applies to:") @> + test <@ appliesToText.Contains("for Stack") @> + test <@ appliesToText.Contains("for Serverless") @> + | _ -> failwith "AppliesTo should not be null" + | _ -> failwith "YamlFrontMatter should not be null"