diff --git a/BootstrapBlazor.Extensions.slnx b/BootstrapBlazor.Extensions.slnx
index e51d5906..21620bd9 100644
--- a/BootstrapBlazor.Extensions.slnx
+++ b/BootstrapBlazor.Extensions.slnx
@@ -124,5 +124,6 @@
+
diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/BootstrapBlazor.LLMsDocsGenerator.csproj b/tools/BootstrapBlazor.LLMsDocsGenerator/BootstrapBlazor.LLMsDocsGenerator.csproj
new file mode 100644
index 00000000..61893ece
--- /dev/null
+++ b/tools/BootstrapBlazor.LLMsDocsGenerator/BootstrapBlazor.LLMsDocsGenerator.csproj
@@ -0,0 +1,30 @@
+
+
+
+ 10.0.0
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+ BootstrapBlazor LLMs docs Generator
+ BootstrapBlazor.LLMsDocsGenerator
+ true
+ llms-docs
+ ice6 (ice6@live.cn)
+ Copyright 2026
+ BootstrapBlazor
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentAnalyzer.cs b/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentAnalyzer.cs
new file mode 100644
index 00000000..a157d6a1
--- /dev/null
+++ b/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentAnalyzer.cs
@@ -0,0 +1,388 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the Apache 2.0 License
+// See the LICENSE file in the project root for more information.
+// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.Text.RegularExpressions;
+
+namespace LlmsDocsGenerator;
+
+///
+/// Analyzes Blazor component source files using Roslyn
+///
+public partial class ComponentAnalyzer
+{
+ private readonly string _sourcePath;
+ private readonly string _componentsPath;
+ private readonly string _samplesPath;
+
+ public ComponentAnalyzer(string sourcePath)
+ {
+ _sourcePath = sourcePath;
+ _componentsPath = Path.Combine(sourcePath, "Components");
+ _samplesPath = Path.Combine(Path.GetDirectoryName(sourcePath)!, "BootstrapBlazor.Server", "Components", "Samples");
+ }
+
+ ///
+ /// Analyze all components in the source directory
+ ///
+ public async Task> AnalyzeAllComponentsAsync()
+ {
+ var components = new List();
+
+ if (!Directory.Exists(_componentsPath))
+ {
+ Console.WriteLine($"Components directory not found: {_componentsPath}");
+ return components;
+ }
+
+ // Find all .razor.cs files
+ var files = Directory.GetFiles(_componentsPath, "*.razor.cs", SearchOption.AllDirectories);
+
+ foreach (var file in files)
+ {
+ var component = await AnalyzeFileAsync(file);
+ if (component != null && component.Parameters.Count > 0)
+ {
+ components.Add(component);
+ }
+ }
+
+ // Also analyze .cs files that might be component base classes
+ var csFiles = Directory.GetFiles(_componentsPath, "*Base.cs", SearchOption.AllDirectories);
+ foreach (var file in csFiles)
+ {
+ var component = await AnalyzeFileAsync(file);
+ if (component != null && component.Parameters.Count > 0)
+ {
+ components.Add(component);
+ }
+ }
+
+ return components.OrderBy(c => c.Name).ToList();
+ }
+
+ ///
+ /// Analyze a specific component by name
+ ///
+ public async Task AnalyzeComponentAsync(string componentName)
+ {
+ var pattern = $"{componentName}.razor.cs";
+ var files = Directory.GetFiles(_componentsPath, pattern, SearchOption.AllDirectories);
+
+ if (files.Length == 0)
+ {
+ // Try without .razor extension
+ pattern = $"{componentName}.cs";
+ files = Directory.GetFiles(_componentsPath, pattern, SearchOption.AllDirectories);
+ }
+
+ if (files.Length == 0)
+ {
+ return null;
+ }
+
+ return await AnalyzeFileAsync(files[0]);
+ }
+
+ private async Task AnalyzeFileAsync(string filePath)
+ {
+ try
+ {
+ var code = await File.ReadAllTextAsync(filePath);
+ var tree = CSharpSyntaxTree.ParseText(code);
+ var root = await tree.GetRootAsync();
+
+ // Find the class declaration
+ var classDeclaration = root.DescendantNodes()
+ .OfType()
+ .FirstOrDefault();
+
+ if (classDeclaration == null)
+ {
+ return null;
+ }
+
+ var component = new ComponentInfo
+ {
+ Name = GetClassName(classDeclaration),
+ FullName = GetFullClassName(classDeclaration, root),
+ Summary = ExtractXmlSummary(classDeclaration),
+ TypeParameters = GetTypeParameters(classDeclaration),
+ BaseClass = GetBaseClass(classDeclaration),
+ SourcePath = GetRelativePath(filePath),
+ LastModified = File.GetLastWriteTimeUtc(filePath),
+ SamplePath = FindSamplePath(GetClassName(classDeclaration))
+ };
+
+ // Extract parameters
+ component.Parameters = ExtractParameters(classDeclaration);
+
+ // Extract public methods
+ component.PublicMethods = ExtractPublicMethods(classDeclaration);
+
+ return component;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error analyzing {filePath}: {ex.Message}");
+ return null;
+ }
+ }
+
+ private string GetClassName(ClassDeclarationSyntax classDeclaration)
+ {
+ return classDeclaration.Identifier.Text;
+ }
+
+ private string GetFullClassName(ClassDeclarationSyntax classDeclaration, SyntaxNode root)
+ {
+ var namespaceName = root.DescendantNodes()
+ .OfType()
+ .FirstOrDefault()?.Name.ToString() ?? "";
+
+ var className = classDeclaration.Identifier.Text;
+
+ if (classDeclaration.TypeParameterList != null)
+ {
+ className += classDeclaration.TypeParameterList.ToString();
+ }
+
+ return string.IsNullOrEmpty(namespaceName) ? className : $"{namespaceName}.{className}";
+ }
+
+ private List GetTypeParameters(ClassDeclarationSyntax classDeclaration)
+ {
+ if (classDeclaration.TypeParameterList == null)
+ {
+ return new List();
+ }
+
+ return classDeclaration.TypeParameterList.Parameters
+ .Select(p => p.Identifier.Text)
+ .ToList();
+ }
+
+ private string? GetBaseClass(ClassDeclarationSyntax classDeclaration)
+ {
+ var baseList = classDeclaration.BaseList;
+ if (baseList == null) return null;
+
+ var baseType = baseList.Types.FirstOrDefault();
+ return baseType?.Type.ToString();
+ }
+
+ private List ExtractParameters(ClassDeclarationSyntax classDeclaration)
+ {
+ var parameters = new List();
+
+ var properties = classDeclaration.DescendantNodes()
+ .OfType();
+
+ foreach (var property in properties)
+ {
+ // Check if property has [Parameter] attribute
+ var hasParameterAttr = property.AttributeLists
+ .SelectMany(a => a.Attributes)
+ .Any(a => a.Name.ToString() is "Parameter" or "ParameterAttribute");
+
+ if (!hasParameterAttr) continue;
+
+ var paramInfo = new ParameterInfo
+ {
+ Name = property.Identifier.Text,
+ Type = SimplifyTypeName(property.Type?.ToString() ?? "unknown"),
+ DefaultValue = GetDefaultValue(property),
+ Description = ExtractXmlSummary(property),
+ IsRequired = HasAttribute(property, "EditorRequired"),
+ IsObsolete = HasAttribute(property, "Obsolete"),
+ ObsoleteMessage = GetObsoleteMessage(property),
+ IsEventCallback = property.Type?.ToString().Contains("EventCallback") ?? false
+ };
+
+ // Skip obsolete parameters
+ if (!paramInfo.IsObsolete)
+ {
+ parameters.Add(paramInfo);
+ }
+ }
+
+ return parameters.OrderBy(p => p.Name).ToList();
+ }
+
+ private List ExtractPublicMethods(ClassDeclarationSyntax classDeclaration)
+ {
+ var methods = new List();
+
+ var methodDeclarations = classDeclaration.DescendantNodes()
+ .OfType()
+ .Where(m => m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.PublicKeyword)));
+
+ foreach (var method in methodDeclarations)
+ {
+ // Skip property accessors, overrides of base class methods
+ if (method.Modifiers.Any(m => m.IsKind(SyntaxKind.OverrideKeyword)))
+ continue;
+
+ var methodInfo = new MethodInfo
+ {
+ Name = method.Identifier.Text,
+ ReturnType = SimplifyTypeName(method.ReturnType.ToString()),
+ Description = ExtractXmlSummary(method),
+ IsJSInvokable = HasAttribute(method, "JSInvokable"),
+ Parameters = method.ParameterList.Parameters
+ .Select(p => (SimplifyTypeName(p.Type?.ToString() ?? ""), p.Identifier.Text))
+ .ToList()
+ };
+
+ methods.Add(methodInfo);
+ }
+
+ return methods;
+ }
+
+ private string? ExtractXmlSummary(SyntaxNode node)
+ {
+ var trivia = node.GetLeadingTrivia();
+ var xmlTrivia = trivia.FirstOrDefault(t =>
+ t.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia) ||
+ t.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia));
+
+ if (xmlTrivia == default)
+ return null;
+
+ var xmlText = xmlTrivia.ToString();
+
+ // Extract content from tags
+ var match = SummaryRegex().Match(xmlText);
+ if (match.Success)
+ {
+ var summary = match.Groups[1].Value;
+ // Clean up the summary
+ summary = CleanXmlComment(summary);
+ return string.IsNullOrWhiteSpace(summary) ? null : summary;
+ }
+
+ return null;
+ }
+
+ private string CleanXmlComment(string comment)
+ {
+ // Remove /// prefixes and extra whitespace
+ var lines = comment.Split('\n')
+ .Select(l => l.Trim().TrimStart('/').Trim())
+ .Where(l => !string.IsNullOrWhiteSpace(l));
+
+ return string.Join(" ", lines);
+ }
+
+ private string? GetDefaultValue(PropertyDeclarationSyntax property)
+ {
+ var initializer = property.Initializer;
+ if (initializer != null)
+ {
+ return SimplifyDefaultValue(initializer.Value.ToString());
+ }
+
+ // Check for default in constructor or OnParametersSet
+ return null;
+ }
+
+ private string SimplifyDefaultValue(string value)
+ {
+ // Simplify common patterns
+ if (value == "false") return "false";
+ if (value == "true") return "true";
+ if (value == "null") return "null";
+ if (value == "0") return "0";
+ if (value == "string.Empty" || value == "\"\"") return "\"\"";
+ if (value.StartsWith("new ")) return "new()";
+
+ return value;
+ }
+
+ private string SimplifyTypeName(string typeName)
+ {
+ // Remove common namespace prefixes
+ var result = typeName
+ .Replace("System.", "")
+ .Replace("Collections.Generic.", "")
+ .Replace("Threading.Tasks.", "");
+
+ // Handle Nullable -> T? (must be done carefully to not break other generics)
+ result = NullableRegex().Replace(result, "$1?");
+
+ // Simplify primitive type names
+ result = result
+ .Replace("Int32", "int")
+ .Replace("Int64", "long")
+ .Replace("Boolean", "bool")
+ .Replace("String", "string")
+ .Replace("Object", "object");
+
+ return result;
+ }
+
+ [GeneratedRegex(@"Nullable<([^>]+)>")]
+ private static partial Regex NullableRegex();
+
+ private bool HasAttribute(MemberDeclarationSyntax member, string attributeName)
+ {
+ return member.AttributeLists
+ .SelectMany(a => a.Attributes)
+ .Any(a => a.Name.ToString() == attributeName ||
+ a.Name.ToString() == attributeName + "Attribute");
+ }
+
+ private string? GetObsoleteMessage(PropertyDeclarationSyntax property)
+ {
+ var obsoleteAttr = property.AttributeLists
+ .SelectMany(a => a.Attributes)
+ .FirstOrDefault(a => a.Name.ToString() is "Obsolete" or "ObsoleteAttribute");
+
+ if (obsoleteAttr?.ArgumentList?.Arguments.Count > 0)
+ {
+ return obsoleteAttr.ArgumentList.Arguments[0].ToString().Trim('"');
+ }
+
+ return null;
+ }
+
+ private string GetRelativePath(string fullPath)
+ {
+ var basePath = Path.GetDirectoryName(Path.GetDirectoryName(_sourcePath))!;
+ return Path.GetRelativePath(basePath, fullPath).Replace('\\', '/');
+ }
+
+ private string? FindSamplePath(string componentName)
+ {
+ if (!Directory.Exists(_samplesPath))
+ return null;
+
+ // Try common sample file naming patterns
+ var patterns = new[]
+ {
+ $"{componentName}s.razor",
+ $"{componentName}.razor",
+ $"{componentName}Demo.razor",
+ $"{componentName}Sample.razor"
+ };
+
+ foreach (var pattern in patterns)
+ {
+ var files = Directory.GetFiles(_samplesPath, pattern, SearchOption.AllDirectories);
+ if (files.Length > 0)
+ {
+ return GetRelativePath(files[0]);
+ }
+ }
+
+ return null;
+ }
+
+ [GeneratedRegex(@"\s*(.*?)\s*", RegexOptions.Singleline)]
+ private static partial Regex SummaryRegex();
+}
diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentInfo.cs b/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentInfo.cs
new file mode 100644
index 00000000..4c7ac9cd
--- /dev/null
+++ b/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentInfo.cs
@@ -0,0 +1,139 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the Apache 2.0 License
+// See the LICENSE file in the project root for more information.
+// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
+
+namespace LlmsDocsGenerator;
+
+///
+/// Represents information about a Blazor component
+///
+public class ComponentInfo
+{
+ ///
+ /// Component name (e.g., "Table", "Button")
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Full type name including namespace
+ ///
+ public string FullName { get; set; } = string.Empty;
+
+ ///
+ /// XML documentation summary
+ ///
+ public string? Summary { get; set; }
+
+ ///
+ /// Generic type parameters (e.g., "TItem", "TValue")
+ ///
+ public List TypeParameters { get; set; } = new();
+
+ ///
+ /// Component parameters ([Parameter] properties)
+ ///
+ public List Parameters { get; set; } = new();
+
+ ///
+ /// Public methods
+ ///
+ public List PublicMethods { get; set; } = new();
+
+ ///
+ /// Base class name
+ ///
+ public string? BaseClass { get; set; }
+
+ ///
+ /// Source file path
+ ///
+ public string SourcePath { get; set; } = string.Empty;
+
+ ///
+ /// Last modification time of the source file
+ ///
+ public DateTime LastModified { get; set; }
+
+ ///
+ /// Related sample file path (if exists)
+ ///
+ public string? SamplePath { get; set; }
+}
+
+///
+/// Represents a component parameter
+///
+public class ParameterInfo
+{
+ ///
+ /// Parameter name
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Parameter type as string
+ ///
+ public string Type { get; set; } = string.Empty;
+
+ ///
+ /// Default value (if any)
+ ///
+ public string? DefaultValue { get; set; }
+
+ ///
+ /// XML documentation summary
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// Whether this is an EditorRequired parameter
+ ///
+ public bool IsRequired { get; set; }
+
+ ///
+ /// Whether this parameter is obsolete
+ ///
+ public bool IsObsolete { get; set; }
+
+ ///
+ /// Obsolete message (if obsolete)
+ ///
+ public string? ObsoleteMessage { get; set; }
+
+ ///
+ /// Whether this is an EventCallback
+ ///
+ public bool IsEventCallback { get; set; }
+}
+
+///
+/// Represents a public method
+///
+public class MethodInfo
+{
+ ///
+ /// Method name
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Return type
+ ///
+ public string ReturnType { get; set; } = string.Empty;
+
+ ///
+ /// Method parameters
+ ///
+ public List<(string Type, string Name)> Parameters { get; set; } = new();
+
+ ///
+ /// XML documentation summary
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// Whether this is a JSInvokable method
+ ///
+ public bool IsJSInvokable { get; set; }
+}
diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/DocsGenerator.cs b/tools/BootstrapBlazor.LLMsDocsGenerator/DocsGenerator.cs
new file mode 100644
index 00000000..f5141654
--- /dev/null
+++ b/tools/BootstrapBlazor.LLMsDocsGenerator/DocsGenerator.cs
@@ -0,0 +1,243 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the Apache 2.0 License
+// See the LICENSE file in the project root for more information.
+// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
+
+namespace LlmsDocsGenerator;
+
+///
+/// Main documentation generator class
+///
+public class DocsGenerator
+{
+ private readonly string _outputPath;
+ private readonly string _componentsOutputPath;
+ private readonly string _sourcePath;
+ private readonly ComponentAnalyzer _analyzer;
+ private readonly MarkdownBuilder _markdownBuilder;
+ private readonly bool _debug;
+
+ public DocsGenerator(string? rootFolder, bool debug)
+ {
+ _debug = debug;
+
+ // Find the source directory (relative to tool location or current directory)
+ var root = FindSourcePath(rootFolder);
+
+ _sourcePath = Path.Combine(root, "src", "BootstrapBlazor");
+ _outputPath = Path.Combine(root, "src", "BootstrapBlazor.Server", "wwwroot", "llms");
+ _componentsOutputPath = Path.Combine(_outputPath, "components");
+ _analyzer = new ComponentAnalyzer(_sourcePath);
+ _markdownBuilder = new MarkdownBuilder();
+ }
+
+ private string FindSourcePath(string? rootFolder)
+ {
+ // Try to find src/BootstrapBlazor from current directory or parent directories
+ var current = rootFolder ?? AppContext.BaseDirectory;
+ Logger($"Root path: {current}");
+
+ while (!string.IsNullOrEmpty(current))
+ {
+ var parent = Directory.GetParent(current);
+ if (parent == null)
+ {
+ break;
+ }
+ if (parent.Name.Equals("BootstrapBlazor", StringComparison.OrdinalIgnoreCase))
+ {
+ return parent.FullName;
+ }
+ current = parent.FullName;
+ }
+
+ throw new DirectoryNotFoundException("Could not find src directory. Please run this tool from the BootstrapBlazor repository root.");
+ }
+
+ ///
+ /// Generate all documentation files
+ ///
+ public async Task GenerateAllAsync()
+ {
+ Logger($"Source path: {_sourcePath}");
+ Logger($"Output path: {_outputPath}");
+ Logger($"Components path: {_componentsOutputPath}");
+
+ // Ensure output directories exist
+ Directory.CreateDirectory(_outputPath);
+ Directory.CreateDirectory(_componentsOutputPath);
+
+ // Analyze all components
+ Logger("Analyzing components...");
+ var components = await _analyzer.AnalyzeAllComponentsAsync();
+ Logger($"Found {components.Count} components");
+
+ // Generate index file
+ await GenerateIndexAsync(components);
+
+ // Generate individual component documentation files
+ Logger("Generating individual component documentation...");
+ foreach (var component in components)
+ {
+ await GenerateComponentDocAsync(component);
+ }
+
+ Logger("Documentation generation complete!");
+ }
+
+ ///
+ /// Generate only the index file
+ ///
+ public async Task GenerateIndexAsync()
+ {
+ var components = await _analyzer.AnalyzeAllComponentsAsync();
+ await GenerateIndexAsync(components);
+ }
+
+ private async Task GenerateIndexAsync(List components)
+ {
+ // Ensure output directory exists
+ Directory.CreateDirectory(_outputPath);
+
+ var indexPath = Path.Combine(_outputPath, "llms.txt");
+ var content = _markdownBuilder.BuildIndex(components);
+ await File.WriteAllTextAsync(indexPath, content);
+ Logger($"Generated: {indexPath}");
+ }
+
+ ///
+ /// Generate documentation for a specific component
+ ///
+ public async Task GenerateComponentAsync(string componentName)
+ {
+ var component = await _analyzer.AnalyzeComponentAsync(componentName);
+ if (component == null)
+ {
+ Logger($"Component not found: {componentName}");
+ return;
+ }
+
+ // Ensure output directory exists
+ Directory.CreateDirectory(_componentsOutputPath);
+
+ await GenerateComponentDocAsync(component);
+ }
+
+ private async Task GenerateComponentDocAsync(ComponentInfo component)
+ {
+ var content = _markdownBuilder.BuildComponentDoc(component);
+ var fileName = $"{component.Name}.txt";
+ var filePath = Path.Combine(_componentsOutputPath, fileName);
+ await File.WriteAllTextAsync(filePath, content);
+ Logger($"Generated: {filePath}");
+ }
+
+ ///
+ /// Check if documentation is up-to-date
+ ///
+ public async Task CheckAsync()
+ {
+ Logger("Checking documentation freshness...");
+
+ var components = await _analyzer.AnalyzeAllComponentsAsync();
+
+ // Check index file
+ var indexPath = Path.Combine(_outputPath, "llms.txt");
+ if (!File.Exists(indexPath))
+ {
+ Logger("OUTDATED: llms.txt does not exist");
+ return false;
+ }
+
+ var indexLastWrite = File.GetLastWriteTimeUtc(indexPath);
+
+ // compute the most recent component source timestamp:
+ var newestComponentWrite = components
+ .Select(c => c.LastModified)
+ .DefaultIfEmpty(indexLastWrite)
+ .Max();
+
+ if (indexLastWrite < newestComponentWrite)
+ {
+ Logger("Index file is stale relative to component sources. Please regenerate docs.");
+ return false;
+ }
+
+ // Check each component file
+ foreach (var component in components)
+ {
+ var fileName = $"{component.Name}.txt";
+ var filePath = Path.Combine(_componentsOutputPath, fileName);
+
+ if (!File.Exists(filePath))
+ {
+ Logger($"OUTDATED: {fileName} does not exist");
+ return false;
+ }
+
+ // Check if source file is newer than the doc file
+ var docLastWrite = File.GetLastWriteTimeUtc(filePath);
+ if (component.LastModified > docLastWrite)
+ {
+ Logger($"OUTDATED: {component.Name} was modified after {fileName}");
+ return false;
+ }
+ }
+
+ Logger("Documentation is up-to-date");
+ return true;
+ }
+
+ private static string GetComponentCategory(string componentName)
+ {
+ return componentName.ToLowerInvariant() switch
+ {
+ // Table family
+ var n when n.Contains("table") => "table",
+
+ // Input family
+ var n when n.Contains("input") || n.Contains("textarea") ||
+ n.Contains("password") || n == "otpinput" => "input",
+
+ // Select family
+ var n when n.Contains("select") || n.Contains("dropdown") ||
+ n.Contains("autocomplete") || n.Contains("cascader") ||
+ n.Contains("transfer") || n.Contains("multiselect") => "select",
+
+ // Button family
+ var n when n.Contains("button") || n == "gotop" ||
+ n.Contains("popconfirm") => "button",
+
+ // Dialog family
+ var n when n.Contains("dialog") || n.Contains("modal") ||
+ n.Contains("drawer") || n.Contains("swal") ||
+ n.Contains("toast") || n.Contains("message") => "dialog",
+
+ // Navigation family
+ var n when n.Contains("menu") || n.Contains("tab") ||
+ n.Contains("breadcrumb") || n.Contains("step") ||
+ n.Contains("anchor") || n.Contains("nav") => "nav",
+
+ // Card/Container family
+ var n when n.Contains("card") || n.Contains("collapse") ||
+ n.Contains("groupbox") || n.Contains("panel") => "card",
+
+ // TreeView
+ var n when n.Contains("tree") => "treeview",
+
+ // Form
+ var n when n.Contains("validateform") || n.Contains("editorform") ||
+ n.Contains("validator") => "form",
+
+ _ => "other"
+ };
+ }
+
+ private void Logger(string message)
+ {
+ if (_debug)
+ {
+ Console.WriteLine(message);
+ }
+ }
+}
diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/MarkdownBuilder.cs b/tools/BootstrapBlazor.LLMsDocsGenerator/MarkdownBuilder.cs
new file mode 100644
index 00000000..97c8271f
--- /dev/null
+++ b/tools/BootstrapBlazor.LLMsDocsGenerator/MarkdownBuilder.cs
@@ -0,0 +1,399 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the Apache 2.0 License
+// See the LICENSE file in the project root for more information.
+// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
+
+using System.Text;
+
+namespace LlmsDocsGenerator;
+
+///
+/// Builds Markdown documentation for components
+///
+public class MarkdownBuilder
+{
+ private const string GitHubBaseUrl = "https://github.com/dotnetcore/BootstrapBlazor/blob/main/";
+ private readonly StringBuilder _sb = new();
+
+ ///
+ /// Build the main llms.txt index file
+ ///
+ public string BuildIndex(List components)
+ {
+ _sb.Clear();
+
+ _sb.AppendLine("# BootstrapBlazor");
+ _sb.AppendLine();
+ _sb.AppendLine("> Enterprise-class Blazor UI component library based on Bootstrap 5");
+ _sb.AppendLine();
+
+ // Quick Start section
+ _sb.AppendLine("## Quick Start");
+ _sb.AppendLine();
+ _sb.AppendLine("```bash");
+ _sb.AppendLine("dotnet add package BootstrapBlazor");
+ _sb.AppendLine("```");
+ _sb.AppendLine();
+ _sb.AppendLine("### Configuration");
+ _sb.AppendLine();
+ _sb.AppendLine("```csharp");
+ _sb.AppendLine("// Program.cs");
+ _sb.AppendLine("builder.Services.AddBootstrapBlazor();");
+ _sb.AppendLine("```");
+ _sb.AppendLine();
+ _sb.AppendLine("```razor");
+ _sb.AppendLine("@* _Imports.razor *@");
+ _sb.AppendLine("@using BootstrapBlazor.Components");
+ _sb.AppendLine("```");
+ _sb.AppendLine();
+ _sb.AppendLine("```html");
+ _sb.AppendLine("");
+ _sb.AppendLine("");
+ _sb.AppendLine("");
+ _sb.AppendLine("```");
+ _sb.AppendLine();
+
+ // Component List - grouped by category for easy navigation
+ _sb.AppendLine("## Components");
+ _sb.AppendLine();
+ _sb.AppendLine("Each component has its own documentation file in the `components/` directory.");
+ _sb.AppendLine("Use `components/{ComponentName}.txt` to get detailed API information.");
+ _sb.AppendLine();
+
+ // Group components by category for the index
+ var categorized = CategorizeComponents(components);
+
+ var categoryDescriptions = new Dictionary
+ {
+ ["table"] = ("Data Display - Table", "Complex data table with sorting, filtering, paging, editing"),
+ ["input"] = ("Form Inputs", "Text input, number input, textarea, date picker"),
+ ["select"] = ("Selection Components", "Select, multi-select, autocomplete, cascader, transfer"),
+ ["button"] = ("Buttons", "Button, button group, dropdown button, split button"),
+ ["dialog"] = ("Dialogs & Feedback", "Modal, drawer, dialog service, message, toast"),
+ ["nav"] = ("Navigation", "Menu, tabs, breadcrumb, steps, pagination"),
+ ["card"] = ("Containers", "Card, collapse, group box, split, layout"),
+ ["treeview"] = ("Tree Components", "TreeView, tree select"),
+ ["form"] = ("Form Validation", "ValidateForm, editor form, validation rules"),
+ ["other"] = ("Other Components", "Miscellaneous components")
+ };
+
+ foreach (var (category, categoryComponents) in categorized.OrderBy(c => c.Key))
+ {
+ if (categoryComponents.Count == 0) continue;
+
+ var (title, description) = categoryDescriptions.GetValueOrDefault(category, (category, ""));
+ _sb.AppendLine($"### {title}");
+ _sb.AppendLine();
+ _sb.AppendLine($"{description}");
+ _sb.AppendLine();
+
+ // List components with links to their individual docs
+ foreach (var component in categoryComponents.OrderBy(c => c.Name))
+ {
+ var summary = !string.IsNullOrEmpty(component.Summary)
+ ? $" - {TruncateSummary(component.Summary, 60)}"
+ : "";
+ _sb.AppendLine($"- [{component.Name}](components/{component.Name}.txt){summary}");
+ }
+ _sb.AppendLine();
+ }
+
+ // Source Code Reference
+ _sb.AppendLine("## Source Code Reference");
+ _sb.AppendLine();
+ _sb.AppendLine("GitHub Repository: https://github.com/dotnetcore/BootstrapBlazor");
+ _sb.AppendLine();
+ _sb.AppendLine("When documentation is insufficient, consult the source code:");
+ _sb.AppendLine();
+ _sb.AppendLine("### File Structure");
+ _sb.AppendLine();
+ _sb.AppendLine("```");
+ _sb.AppendLine($"{GitHubBaseUrl}src/BootstrapBlazor/Components/{{ComponentName}}/");
+ _sb.AppendLine("├── {Component}.razor # Razor template");
+ _sb.AppendLine("├── {Component}.razor.cs # Component logic & parameters");
+ _sb.AppendLine("├── {Component}Base.cs # Base class (if exists)");
+ _sb.AppendLine("├── {Component}Option.cs # Configuration options");
+ _sb.AppendLine("└── {Component}Service.cs # Service class (Dialog, Toast, etc.)");
+ _sb.AppendLine("```");
+ _sb.AppendLine();
+ _sb.AppendLine("### Examples");
+ _sb.AppendLine();
+ _sb.AppendLine("```");
+ _sb.AppendLine($"{GitHubBaseUrl}src/BootstrapBlazor.Server/Components/Samples/{{ComponentName}}s.razor");
+ _sb.AppendLine("```");
+ _sb.AppendLine();
+ _sb.AppendLine("### Reading Component Parameters");
+ _sb.AppendLine();
+ _sb.AppendLine("Look for properties with `[Parameter]` attribute:");
+ _sb.AppendLine();
+ _sb.AppendLine("```csharp");
+ _sb.AppendLine("/// ");
+ _sb.AppendLine("/// Gets or sets whether to show the toolbar");
+ _sb.AppendLine("/// ");
+ _sb.AppendLine("[Parameter]");
+ _sb.AppendLine("public bool ShowToolbar { get; set; }");
+ _sb.AppendLine("```");
+ _sb.AppendLine();
+
+ // Footer
+ _sb.AppendLine("---");
+ _sb.AppendLine($"Generated: {DateTime.UtcNow:yyyy-MM-dd}");
+ _sb.AppendLine($"Total Components: {components.Count}");
+ _sb.AppendLine($"Repository: {GitHubBaseUrl}");
+
+ return _sb.ToString();
+ }
+
+ private static Dictionary> CategorizeComponents(List components)
+ {
+ var categories = new Dictionary>
+ {
+ ["table"] = [],
+ ["input"] = [],
+ ["select"] = [],
+ ["button"] = [],
+ ["dialog"] = [],
+ ["nav"] = [],
+ ["card"] = [],
+ ["treeview"] = [],
+ ["form"] = [],
+ ["other"] = []
+ };
+
+ foreach (var component in components)
+ {
+ var category = GetComponentCategory(component.Name);
+ if (categories.TryGetValue(category, out var list))
+ {
+ list.Add(component);
+ }
+ else
+ {
+ categories["other"].Add(component);
+ }
+ }
+
+ // Remove empty categories
+ return categories.Where(c => c.Value.Count > 0)
+ .ToDictionary(c => c.Key, c => c.Value);
+ }
+
+ private static string GetComponentCategory(string componentName)
+ {
+ return componentName.ToLowerInvariant() switch
+ {
+ var n when n.Contains("table") => "table",
+ var n when n.Contains("input") || n.Contains("textarea") ||
+ n.Contains("password") || n == "otpinput" => "input",
+ var n when n.Contains("select") || n.Contains("dropdown") ||
+ n.Contains("autocomplete") || n.Contains("cascader") ||
+ n.Contains("transfer") || n.Contains("multiselect") => "select",
+ var n when n.Contains("button") || n == "gotop" ||
+ n.Contains("popconfirm") => "button",
+ var n when n.Contains("dialog") || n.Contains("modal") ||
+ n.Contains("drawer") || n.Contains("swal") ||
+ n.Contains("toast") || n.Contains("message") => "dialog",
+ var n when n.Contains("menu") || n.Contains("tab") ||
+ n.Contains("breadcrumb") || n.Contains("step") ||
+ n.Contains("anchor") || n.Contains("nav") => "nav",
+ var n when n.Contains("card") || n.Contains("collapse") ||
+ n.Contains("groupbox") || n.Contains("panel") => "card",
+ var n when n.Contains("tree") => "treeview",
+ var n when n.Contains("validateform") || n.Contains("editorform") ||
+ n.Contains("validator") => "form",
+ _ => "other"
+ };
+ }
+
+ private static string TruncateSummary(string summary, int maxLength)
+ {
+ if (string.IsNullOrEmpty(summary)) return "";
+ summary = summary.Replace("\n", " ").Replace("\r", "").Trim();
+ return summary.Length <= maxLength ? summary : summary[..(maxLength - 3)] + "...";
+ }
+
+ ///
+ /// Build documentation for a single component
+ ///
+ public string BuildComponentDoc(ComponentInfo component)
+ {
+ _sb.Clear();
+
+ _sb.AppendLine($"# BootstrapBlazor {component.Name}");
+ _sb.AppendLine();
+
+ if (!string.IsNullOrEmpty(component.Summary))
+ {
+ _sb.AppendLine($"> {component.Summary}");
+ _sb.AppendLine();
+ }
+
+ BuildComponentSection(component, includeHeader: false);
+
+ // Footer
+ _sb.AppendLine("---");
+ _sb.AppendLine($"");
+
+ return _sb.ToString();
+ }
+
+ private void BuildComponentSection(ComponentInfo component, bool includeHeader = true)
+ {
+ if (includeHeader)
+ {
+ _sb.AppendLine($"## {component.Name}");
+ _sb.AppendLine();
+
+ if (!string.IsNullOrEmpty(component.Summary))
+ {
+ _sb.AppendLine(component.Summary);
+ _sb.AppendLine();
+ }
+ }
+
+ // Type parameters
+ if (component.TypeParameters.Count > 0)
+ {
+ _sb.AppendLine("### Type Parameters");
+ _sb.AppendLine();
+ foreach (var tp in component.TypeParameters)
+ {
+ _sb.AppendLine($"- `{tp}` - Generic type parameter");
+ }
+ _sb.AppendLine();
+ }
+
+ // Base class info
+ if (!string.IsNullOrEmpty(component.BaseClass))
+ {
+ _sb.AppendLine($"**Inherits from**: `{component.BaseClass}`");
+ _sb.AppendLine();
+ }
+
+ // Parameters table
+ if (component.Parameters.Count > 0)
+ {
+ _sb.AppendLine("### Parameters");
+ _sb.AppendLine();
+ _sb.AppendLine("");
+ _sb.AppendLine();
+ _sb.AppendLine("| Parameter | Type | Default | Description |");
+ _sb.AppendLine("|-----------|------|---------|-------------|");
+
+ // Sort: required first, then events, then alphabetically
+ var sortedParams = component.Parameters
+ .OrderByDescending(p => p.IsRequired)
+ .ThenBy(p => p.IsEventCallback)
+ .ThenBy(p => p.Name);
+
+ foreach (var param in sortedParams)
+ {
+ var required = param.IsRequired ? " **[Required]**" : "";
+ var description = EscapeMarkdownCell(param.Description ?? "") + required;
+ var defaultVal = param.DefaultValue ?? "-";
+ var type = EscapeMarkdownCell(param.Type);
+
+ _sb.AppendLine($"| {param.Name} | `{type}` | {defaultVal} | {description} |");
+ }
+
+ _sb.AppendLine();
+ _sb.AppendLine("");
+ _sb.AppendLine();
+ }
+
+ // Event callbacks (separate section for clarity)
+ var eventCallbacks = component.Parameters.Where(p => p.IsEventCallback).ToList();
+ if (eventCallbacks.Count > 0)
+ {
+ _sb.AppendLine("### Event Callbacks");
+ _sb.AppendLine();
+ _sb.AppendLine("| Event | Type | Description |");
+ _sb.AppendLine("|-------|------|-------------|");
+
+ foreach (var evt in eventCallbacks.OrderBy(e => e.Name))
+ {
+ var description = EscapeMarkdownCell(evt.Description ?? "");
+ var type = EscapeMarkdownCell(evt.Type);
+ _sb.AppendLine($"| {evt.Name} | `{type}` | {description} |");
+ }
+
+ _sb.AppendLine();
+ }
+
+ // Public methods
+ if (component.PublicMethods.Count > 0)
+ {
+ _sb.AppendLine("### Public Methods");
+ _sb.AppendLine();
+
+ foreach (var method in component.PublicMethods.OrderBy(m => m.Name))
+ {
+ var paramStr = string.Join(", ", method.Parameters.Select(p => $"{p.Item1} {p.Item2}"));
+ _sb.AppendLine($"- `{method.ReturnType} {method.Name}({paramStr})`");
+ if (!string.IsNullOrEmpty(method.Description))
+ {
+ _sb.AppendLine($" - {method.Description}");
+ }
+ if (method.IsJSInvokable)
+ {
+ _sb.AppendLine(" - *[JSInvokable]*");
+ }
+ }
+
+ _sb.AppendLine();
+ }
+
+ // Source reference with GitHub URLs
+ if (!string.IsNullOrEmpty(component.SourcePath))
+ {
+ _sb.AppendLine("### Source");
+ _sb.AppendLine();
+ var sourceUrl = $"{GitHubBaseUrl}{component.SourcePath}";
+ _sb.AppendLine($"- Component: [{component.SourcePath}]({sourceUrl})");
+ if (!string.IsNullOrEmpty(component.SamplePath))
+ {
+ var sampleUrl = $"{GitHubBaseUrl}{component.SamplePath}";
+ _sb.AppendLine($"- Examples: [{component.SamplePath}]({sampleUrl})");
+ }
+ _sb.AppendLine();
+ }
+ }
+
+ ///
+ /// Build a minimal parameter table for embedding in existing docs
+ ///
+ public string BuildParameterTable(List parameters)
+ {
+ _sb.Clear();
+
+ _sb.AppendLine("| Parameter | Type | Default | Description |");
+ _sb.AppendLine("|-----------|------|---------|-------------|");
+
+ var sortedParams = parameters
+ .OrderByDescending(p => p.IsRequired)
+ .ThenBy(p => p.IsEventCallback)
+ .ThenBy(p => p.Name);
+
+ foreach (var param in sortedParams)
+ {
+ var required = param.IsRequired ? " **[Required]**" : "";
+ var description = EscapeMarkdownCell(param.Description ?? "") + required;
+ var defaultVal = param.DefaultValue ?? "-";
+ var type = EscapeMarkdownCell(param.Type);
+
+ _sb.AppendLine($"| {param.Name} | `{type}` | {defaultVal} | {description} |");
+ }
+
+ return _sb.ToString();
+ }
+
+ private static string EscapeMarkdownCell(string text)
+ {
+ if (string.IsNullOrEmpty(text)) return "";
+
+ return text
+ .Replace("|", "\\|")
+ .Replace("\n", " ")
+ .Replace("\r", "");
+ }
+}
diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/Program.cs b/tools/BootstrapBlazor.LLMsDocsGenerator/Program.cs
new file mode 100644
index 00000000..88eec57f
--- /dev/null
+++ b/tools/BootstrapBlazor.LLMsDocsGenerator/Program.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the Apache 2.0 License
+// See the LICENSE file in the project root for more information.
+// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
+
+using LlmsDocsGenerator;
+using System.CommandLine;
+
+var componentOption = new Option("--component") { Description = "Generate documentation for a specific component only" };
+var indexOnlyOption = new Option("--index-only") { Description = "Generate only the index file (llms.txt)" };
+var checkOption = new Option("--check") { Description = "Check if documentation is up-to-date (for CI/CD)" };
+var rootFolderOption = new Option("--root") { Description = "Set the root folder of project" };
+var debugOption = new Option("--debug") { Description = "Set the environment to development and display debugging information." };
+
+var rootCommand = new RootCommand("BootstrapBlazor LLMs Documentation Generator")
+{
+ componentOption,
+ indexOnlyOption,
+ checkOption,
+ rootFolderOption,
+ debugOption
+};
+
+rootCommand.SetAction(async result =>
+{
+ var debug = result.GetValue(debugOption);
+ var rootFolder = result.GetValue(rootFolderOption);
+ var generator = new DocsGenerator(rootFolder, debug);
+
+ var check = result.GetValue(checkOption);
+ if (check)
+ {
+ var isUpToDate = await generator.CheckAsync();
+ Environment.ExitCode = isUpToDate ? 0 : 1;
+ return;
+ }
+
+ var indexOnly = result.GetValue(indexOnlyOption);
+ if (indexOnly)
+ {
+ await generator.GenerateIndexAsync();
+ return;
+ }
+
+ var component = result.GetValue(componentOption);
+ if (!string.IsNullOrEmpty(component))
+ {
+ await generator.GenerateComponentAsync(component);
+ return;
+ }
+
+ await generator.GenerateAllAsync();
+});
+
+return await rootCommand.Parse(args).InvokeAsync();
diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/README.md b/tools/BootstrapBlazor.LLMsDocsGenerator/README.md
new file mode 100644
index 00000000..246c5d66
--- /dev/null
+++ b/tools/BootstrapBlazor.LLMsDocsGenerator/README.md
@@ -0,0 +1,245 @@
+# LlmsDocsGenerator
+
+A tool that automatically generates LLM-friendly documentation for BootstrapBlazor components.
+
+## Purpose
+
+AI coding assistants (Claude Code, Cursor, GitHub Copilot) often generate incorrect UI code because they lack accurate component API information. This tool solves that problem by:
+
+1. **Auto-generating parameter tables** from source code using Roslyn
+2. **Providing GitHub source links** for deeper reference
+3. **Integrating with CI/CD** to keep docs synchronized with code
+
+## Architecture
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ LlmsDocsGenerator │
+├─────────────────────────────────────────────────────────────┤
+│ ComponentAnalyzer → Roslyn-based source code parser │
+│ MarkdownBuilder → Generates markdown documentation │
+│ DocsGenerator → Orchestrates the generation flow │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Output: wwwroot/llms/ │
+├─────────────────────────────────────────────────────────────┤
+│ llms.txt → Index with quick start guide │
+│ components/ → Individual component documentation │
+│ ├── Button.txt → Button component API reference │
+│ ├── Table.txt → Table component API reference │
+│ ├── Select.txt → Select component API reference │
+│ ├── Modal.txt → Modal component API reference │
+│ └── ... → One file per component │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Why One File Per Component?
+
+This design optimizes for LLM and Code Agent consumption:
+
+| Aspect | Per-Category (Old) | Per-Component (New) |
+|--------|-------------------|---------------------|
+| **Precision** | ❌ Loads unrelated components | ✅ Only needed API info |
+| **Token Efficiency** | ❌ Wastes tokens on irrelevant data | ✅ Minimal context loading |
+| **Cache Friendly** | ❌ Regenerates entire category | ✅ Updates single file |
+| **RAG Retrieval** | ❌ Coarse-grained matches | ✅ Fine-grained matches |
+| **Incremental Updates** | ❌ Complex CI/CD checks | ✅ Simple file mapping |
+
+## How It Works
+
+### 1. Source Code Analysis
+
+The `ComponentAnalyzer` uses Roslyn to parse C# source files:
+
+```csharp
+// Scans for [Parameter] attributes
+var parameters = classDeclaration.DescendantNodes()
+ .OfType()
+ .Where(p => HasAttribute(p, "Parameter"));
+
+// Extracts XML documentation comments
+var summary = ExtractXmlSummary(property);
+```
+
+### 2. Documentation Generation
+
+The `MarkdownBuilder` creates structured markdown with:
+
+- Parameter tables (name, type, default, description)
+- Event callbacks section
+- Public methods
+- GitHub source links
+
+### 3. Component Organization
+
+Components are organized in the index by category for easy navigation, but each component has its own dedicated documentation file:
+
+| Category | Example Components |
+|----------|-------------------|
+| table | Table, SelectTable, TableToolbar |
+| input | BootstrapInput, Textarea, OtpInput |
+| select | Select, AutoComplete, Cascader |
+| button | Button, PopConfirmButton |
+| dialog | Modal, Drawer, Toast |
+| nav | Menu, Tab, Breadcrumb |
+| card | Card, Collapse, GroupBox |
+| treeview | TreeView, Tree |
+| form | ValidateForm, EditorForm |
+| other | All other components |
+
+## Installation
+
+### Install as Global Tool
+
+```bash
+dotnet pack tools/LlmsDocsGenerator
+dotnet tool install --global --add-source ./tools/LlmsDocsGenerator/bin/Release BootstrapBlazor.LlmsDocsGenerator
+```
+
+Or install from NuGet (once published):
+
+```bash
+dotnet tool install --global BootstrapBlazor.LlmsDocsGenerator
+```
+
+### Update Tool
+
+```bash
+dotnet tool update --global BootstrapBlazor.LlmsDocsGenerator
+```
+
+### Uninstall Tool
+
+```bash
+dotnet tool uninstall --global BootstrapBlazor.LlmsDocsGenerator
+```
+
+## Usage
+
+Once installed as a global tool, use the `llms-docs` command:
+
+### Generate All Documentation
+
+```bash
+llms-docs
+```
+
+Or when running from source:
+
+```bash
+dotnet run --project tools/LlmsDocsGenerator
+```
+
+### Generate Specific Component
+
+```bash
+llms-docs --component Table
+```
+
+### Generate Index Only
+
+```bash
+llms-docs --index-only
+```
+
+### Check Freshness (CI/CD)
+
+```bash
+llms-docs --check
+```
+
+Returns exit code 1 if documentation is outdated.
+
+### Custom Output Directory
+
+```bash
+llms-docs --output ./docs
+```
+
+### Show Help
+
+```bash
+llms-docs --help
+```
+
+## CI/CD Integration
+
+### Build Workflow (build.yml)
+
+Checks if documentation is up-to-date on every push to main:
+
+```yaml
+- name: Check LLM Documentation
+ run: dotnet run --project tools/LlmsDocsGenerator -- --check
+```
+
+### Docker Workflow (docker.yml)
+
+Regenerates documentation before building the doc site:
+
+```yaml
+- name: Generate LLM Documentation
+ run: dotnet run --project tools/LlmsDocsGenerator
+```
+
+### Dockerfile
+
+Generates documentation during container build:
+
+```dockerfile
+WORKDIR /tools/LlmsDocsGenerator
+RUN dotnet run
+```
+
+## Output Format
+
+Each component documentation includes:
+
+```markdown
+## ComponentName
+
+Description from XML comments
+
+### Type Parameters
+- `TItem` - Generic type parameter
+
+### Parameters
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| Items | `List` | - | Data source |
+| ShowToolbar | `bool` | false | Show toolbar |
+
+### Event Callbacks
+| Event | Type | Description |
+|-------|------|-------------|
+| OnClick | `EventCallback` | Click handler |
+
+### Public Methods
+- `Task RefreshAsync()` - Refresh data
+
+### Source
+- Component: [src/.../Component.razor.cs](GitHub URL)
+- Examples: [src/.../Samples/Components.razor](GitHub URL)
+```
+
+## For Library Users
+
+Users can reference this documentation in their own projects by creating a `llms.txt`:
+
+```markdown
+# My Project
+
+## Dependencies
+
+### BootstrapBlazor
+- Documentation Index: https://www.blazor.zone/llms/llms.txt
+- Button: https://www.blazor.zone/llms/components/Button.txt
+- Table: https://www.blazor.zone/llms/components/Table.txt
+- Modal: https://www.blazor.zone/llms/components/Modal.txt
+```
+
+LLM agents can:
+1. First read `llms.txt` to discover available components
+2. Then fetch specific `components/{ComponentName}.txt` for detailed API info
diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/README.zh-CN.md b/tools/BootstrapBlazor.LLMsDocsGenerator/README.zh-CN.md
new file mode 100644
index 00000000..e3163ed3
--- /dev/null
+++ b/tools/BootstrapBlazor.LLMsDocsGenerator/README.zh-CN.md
@@ -0,0 +1,266 @@
+# LlmsDocsGenerator
+
+自动为 BootstrapBlazor 组件生成 LLM 友好文档的工具。
+
+## 目的
+
+AI 编程助手(Claude Code、Cursor、GitHub Copilot)经常因为缺乏准确的组件 API 信息而生成错误的 UI 代码。本工具通过以下方式解决这个问题:
+
+1. **使用 Roslyn 自动生成参数表** - 从源代码提取
+2. **提供 GitHub 源码链接** - 方便深入查阅
+3. **集成 CI/CD** - 确保文档与代码同步
+
+## 架构
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ LlmsDocsGenerator │
+├─────────────────────────────────────────────────────────────┤
+│ ComponentAnalyzer → 基于 Roslyn 的源码解析器 │
+│ MarkdownBuilder → 生成 Markdown 文档 │
+│ DocsGenerator → 协调生成流程 │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ 输出目录: wwwroot/llms/ │
+├─────────────────────────────────────────────────────────────┤
+│ llms.txt → 索引文件,包含快速入门指南 │
+│ components/ → 单独的组件文档目录 │
+│ ├── Button.txt → Button 组件 API 参考 │
+│ ├── Table.txt → Table 组件 API 参考 │
+│ ├── Select.txt → Select 组件 API 参考 │
+│ ├── Modal.txt → Modal 组件 API 参考 │
+│ └── ... → 每个组件一个文件 │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### 为什么每个组件单独一个文件?
+
+这种设计针对 LLM 和 Code Agent 进行了优化:
+
+| 方面 | 按分类(旧方案) | 按组件(新方案) |
+|------|-----------------|-----------------|
+| **精确性** | ❌ 加载无关组件 | ✅ 只加载需要的 API |
+| **Token 效率** | ❌ 浪费 token 在无关数据上 | ✅ 最小化上下文加载 |
+| **缓存友好** | ❌ 需重新生成整个分类 | ✅ 只更新单个文件 |
+| **RAG 检索** | ❌ 粗粒度匹配 | ✅ 细粒度匹配 |
+| **增量更新** | ❌ CI/CD 检查复杂 | ✅ 简单的文件映射 |
+
+## 工作原理
+
+### 1. 源码分析
+
+`ComponentAnalyzer` 使用 Roslyn 解析 C# 源文件:
+
+```csharp
+// 扫描 [Parameter] 特性
+var parameters = classDeclaration.DescendantNodes()
+ .OfType()
+ .Where(p => HasAttribute(p, "Parameter"));
+
+// 提取 XML 文档注释
+var summary = ExtractXmlSummary(property);
+```
+
+### 2. 文档生成
+
+`MarkdownBuilder` 生成结构化的 Markdown,包含:
+
+- 参数表(名称、类型、默认值、描述)
+- 事件回调部分
+- 公共方法
+- GitHub 源码链接
+
+### 3. 组件组织
+
+组件在索引中按类别组织便于导航,但每个组件有独立的文档文件:
+
+| 类别 | 组件示例 |
+|------|----------|
+| table | Table, SelectTable, TableToolbar |
+| input | BootstrapInput, Textarea, OtpInput |
+| select | Select, AutoComplete, Cascader |
+| button | Button, PopConfirmButton |
+| dialog | Modal, Drawer, Toast |
+| nav | Menu, Tab, Breadcrumb |
+| card | Card, Collapse, GroupBox |
+| treeview | TreeView, Tree |
+| form | ValidateForm, EditorForm |
+| other | 其他所有组件 |
+
+## 安装
+
+### 作为全局工具安装
+
+```bash
+dotnet pack tools/LlmsDocsGenerator
+dotnet tool install --global --add-source ./tools/LlmsDocsGenerator/bin/Release BootstrapBlazor.LlmsDocsGenerator
+```
+
+或从 NuGet 安装(发布后):
+
+```bash
+dotnet tool install --global BootstrapBlazor.LlmsDocsGenerator
+```
+
+### 更新工具
+
+```bash
+dotnet tool update --global BootstrapBlazor.LlmsDocsGenerator
+```
+
+### 卸载工具
+
+```bash
+dotnet tool uninstall --global BootstrapBlazor.LlmsDocsGenerator
+```
+
+## 使用方法
+
+安装为全局工具后,使用 `llms-docs` 命令:
+
+### 生成所有文档
+
+```bash
+llms-docs
+```
+
+或从源代码运行:
+
+```bash
+dotnet run --project tools/LlmsDocsGenerator
+```
+
+### 生成特定组件
+
+```bash
+llms-docs --component Table
+```
+
+### 仅生成索引
+
+```bash
+llms-docs --index-only
+```
+
+### 检查文档是否过期(CI/CD)
+
+```bash
+llms-docs --check
+```
+
+如果文档过期,返回退出码 1。
+
+### 自定义输出目录
+
+```bash
+llms-docs --output ./docs
+```
+
+### 显示帮助
+
+```bash
+llms-docs --help
+```
+
+## CI/CD 集成
+
+### 构建工作流 (build.yml)
+
+每次推送到 main 分支时检查文档是否最新:
+
+```yaml
+- name: Check LLM Documentation
+ run: dotnet run --project tools/LlmsDocsGenerator -- --check
+```
+
+### Docker 工作流 (docker.yml)
+
+构建文档站点前重新生成文档:
+
+```yaml
+- name: Generate LLM Documentation
+ run: dotnet run --project tools/LlmsDocsGenerator
+```
+
+### Dockerfile
+
+容器构建时生成文档:
+
+```dockerfile
+WORKDIR /tools/LlmsDocsGenerator
+RUN dotnet run
+```
+
+## 输出格式
+
+每个组件的文档包含:
+
+```markdown
+## 组件名称
+
+来自 XML 注释的描述
+
+### 类型参数
+- `TItem` - 泛型类型参数
+
+### 参数
+| 参数 | 类型 | 默认值 | 描述 |
+|------|------|--------|------|
+| Items | `List` | - | 数据源 |
+| ShowToolbar | `bool` | false | 显示工具栏 |
+
+### 事件回调
+| 事件 | 类型 | 描述 |
+|------|------|------|
+| OnClick | `EventCallback` | 点击处理器 |
+
+### 公共方法
+- `Task RefreshAsync()` - 刷新数据
+
+### 源码
+- 组件: [src/.../Component.razor.cs](GitHub 链接)
+- 示例: [src/.../Samples/Components.razor](GitHub 链接)
+```
+
+## 库用户使用指南
+
+用户可以在自己的项目中创建 `llms.txt` 来引用本文档:
+
+```markdown
+# 我的项目
+
+## 依赖
+
+### BootstrapBlazor
+- 文档索引: https://www.blazor.zone/llms/llms.txt
+- Button: https://www.blazor.zone/llms/components/Button.txt
+- Table: https://www.blazor.zone/llms/components/Table.txt
+- Modal: https://www.blazor.zone/llms/components/Modal.txt
+```
+
+LLM 代理可以:
+1. 先读取 `llms.txt` 了解有哪些组件
+2. 然后获取 `components/{ComponentName}.txt` 获取详细 API 信息
+
+## 设计理念
+
+### 为什么需要这个工具?
+
+| 问题 | 解决方案 |
+|------|----------|
+| AI 生成错误的组件代码 | 提供准确的参数文档 |
+| 手动维护文档容易过期 | 自动从源码生成 |
+| 文档太大占用上下文 | 每组件单独文件,按需加载 |
+| 用户不知道如何引用 | 提供项目模板 |
+
+### 混合文档策略
+
+```
+AI 代理工作流程:
+1. 读取 llms.txt (轻量索引) → 快速了解组件列表
+2. 按需读取 components/{Component}.txt → 获取精确的 API 参考
+3. 不确定时查阅 GitHub 源码 → 获取准确信息
+4. 参考 Samples 目录 → 学习官方用法
+```