Skip to content

feat(llms): add cli tools for llms#7461

Merged
ArgoZhang merged 35 commits intodotnetcore:mainfrom
ice6:main
Jan 3, 2026
Merged

feat(llms): add cli tools for llms#7461
ArgoZhang merged 35 commits intodotnetcore:mainfrom
ice6:main

Conversation

@ice6
Copy link
Copy Markdown
Contributor

@ice6 ice6 commented Jan 1, 2026

Link issues

fixes #7400

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Add an automated LLM-focused documentation generation system for BootstrapBlazor components and integrate it into docs, build, and Docker workflows.

New Features:

  • Introduce an LlmsDocsGenerator Roslyn-based tool that analyzes BootstrapBlazor components and generates LLM-oriented Markdown documentation by category and per component.
  • Publish generated LLM documentation files under wwwroot/llmstxt with an example project template and category-specific reference files for use by AI code assistants.
  • Document how to consume the LLM docs from external projects in the README, including online URLs and local regeneration commands.

Enhancements:

  • Integrate LLM documentation generation and freshness checks into CI build workflow and Docker image build to ensure docs are generated and kept up to date.
  • Improve component XML documentation text for Button and DateTime filter components to fix minor wording/typo issues.

Build:

  • Update the server Dockerfile to run the LlmsDocsGenerator tool during the build stage so images contain up-to-date LLM docs.

CI:

  • Extend build workflow to run the LlmsDocsGenerator in check mode to validate documentation freshness.
  • Update Docker build workflow to install .NET 8 SDK and run the LLM docs generator before building images.

Documentation:

  • Expand README with a new section explaining the LLM-friendly documentation endpoints and usage patterns for AI code agents.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Jan 1, 2026

Reviewer's Guide

Introduce an LlmsDocsGenerator tooling project that analyzes BootstrapBlazor components with Roslyn to generate llms*.txt markdown docs for AI code agents, ship the generated files with the server app, and integrate generation/freshness checks into Docker, CI build, and documentation, along with a few minor comment/typo fixes in existing components.

Sequence diagram for CI documentation freshness check

sequenceDiagram
    actor Dev as Developer
    participant GH as GitHubActions_build.yml
    participant CLI as LlmsDocsGenerator_Program
    participant DG as DocsGenerator
    participant CA as ComponentAnalyzer

    Dev->>GH: Push changes / open PR
    GH->>GH: Run job Check LLM Documentation
    GH->>CLI: dotnet run --project tools/LlmsDocsGenerator -- --check

    CLI->>DG: new DocsGenerator(outputPath)
    CLI->>DG: CheckAsync()

    DG->>CA: AnalyzeAllComponentsAsync()
    CA-->>DG: List<ComponentInfo>

    DG->>DG: CategorizeComponents(components)
    DG->>DG: Read llms.txt and llms-*.txt timestamps
    DG->>DG: Compare with component LastModified times

    alt any doc missing or stale
        DG-->>CLI: return false
        CLI-->>GH: exit code 1
        GH-->>Dev: Mark workflow as failed
    else docs up-to-date
        DG-->>CLI: return true
        CLI-->>GH: exit code 0
        GH-->>Dev: Workflow succeeds
    end
Loading

Class diagram for LlmsDocsGenerator core types

classDiagram
    class DocsGenerator {
        - string _outputPath
        - string _sourcePath
        - ComponentAnalyzer _analyzer
        - MarkdownBuilder _markdownBuilder
        + DocsGenerator(string outputPath)
        + Task GenerateAllAsync()
        + Task GenerateIndexAsync()
        + Task GenerateComponentAsync(string componentName)
        + Task~bool~ CheckAsync()
        - string FindSourcePath()
        - Dictionary~string, List~ComponentInfo~~ CategorizeComponents(List~ComponentInfo~ components)
        - string GetComponentCategory(string componentName)
        - string GetCategoryFileName(string category)
        - Task GenerateCategoryDocAsync(string category, List~ComponentInfo~ components)
    }

    class ComponentAnalyzer {
        - string _sourcePath
        - string _componentsPath
        - string _samplesPath
        + ComponentAnalyzer(string sourcePath)
        + Task~List~ComponentInfo~~ AnalyzeAllComponentsAsync()
        + Task~ComponentInfo?~ AnalyzeComponentAsync(string componentName)
        - Task~ComponentInfo?~ AnalyzeFileAsync(string filePath)
        - string GetClassName(ClassDeclarationSyntax classDeclaration)
        - string GetFullClassName(ClassDeclarationSyntax classDeclaration, SyntaxNode root)
        - List~string~ GetTypeParameters(ClassDeclarationSyntax classDeclaration)
        - string? GetBaseClass(ClassDeclarationSyntax classDeclaration)
        - List~ParameterInfo~ ExtractParameters(ClassDeclarationSyntax classDeclaration)
        - List~MethodInfo~ ExtractPublicMethods(ClassDeclarationSyntax classDeclaration)
        - string? ExtractXmlSummary(SyntaxNode node)
        - string CleanXmlComment(string comment)
        - string? GetDefaultValue(PropertyDeclarationSyntax property)
        - string SimplifyDefaultValue(string value)
        - string SimplifyTypeName(string typeName)
        - bool HasAttribute(MemberDeclarationSyntax member, string attributeName)
        - string? GetObsoleteMessage(PropertyDeclarationSyntax property)
        - string GetRelativePath(string fullPath)
        - string? FindSamplePath(string componentName)
    }

    class MarkdownBuilder {
        - const string GitHubBaseUrl
        - StringBuilder _sb
        + string BuildIndex(Dictionary~string, List~ComponentInfo~~ categorizedComponents)
        + string BuildCategoryDoc(string category, List~ComponentInfo~ components)
        + string BuildComponentDoc(ComponentInfo component)
        + string BuildParameterTable(List~ParameterInfo~ parameters)
        - void BuildComponentSection(ComponentInfo component, bool includeHeader)
        - string EscapeMarkdownCell(string text)
    }

    class ComponentInfo {
        + string Name
        + string FullName
        + string? Summary
        + List~string~ TypeParameters
        + List~ParameterInfo~ Parameters
        + List~MethodInfo~ PublicMethods
        + string? BaseClass
        + string SourcePath
        + DateTime LastModified
        + string? SamplePath
    }

    class ParameterInfo {
        + string Name
        + string Type
        + string? DefaultValue
        + string? Description
        + bool IsRequired
        + bool IsObsolete
        + string? ObsoleteMessage
        + bool IsEventCallback
    }

    class MethodInfo {
        + string Name
        + string ReturnType
        + List~(string Type, string Name)~ Parameters
        + string? Description
        + bool IsJSInvokable
    }

    DocsGenerator --> ComponentAnalyzer
    DocsGenerator --> MarkdownBuilder
    DocsGenerator --> ComponentInfo
    ComponentAnalyzer --> ComponentInfo
    ComponentInfo --> ParameterInfo
    ComponentInfo --> MethodInfo
Loading

File-Level Changes

Change Details Files
Add LlmsDocsGenerator CLI tool to analyze Blazor components and generate LLM-oriented markdown documentation files.
  • Create Roslyn-based ComponentAnalyzer to scan component/source directories and extract parameters, methods, and metadata.
  • Implement MarkdownBuilder to render index/category/component markdown into llms*.txt files with parameter tables, event sections, and GitHub source links.
  • Create DocsGenerator orchestrator that categorizes components, writes index/category docs, and supports freshness checks via file timestamps.
  • Add Program.cs using System.CommandLine to expose CLI options like --component, --index-only, --check, and --output.
  • Define ComponentInfo, ParameterInfo, and MethodInfo data models plus LlmsDocsGenerator.csproj to build the tool.
tools/LlmsDocsGenerator/ComponentAnalyzer.cs
tools/LlmsDocsGenerator/MarkdownBuilder.cs
tools/LlmsDocsGenerator/DocsGenerator.cs
tools/LlmsDocsGenerator/ComponentInfo.cs
tools/LlmsDocsGenerator/Program.cs
tools/LlmsDocsGenerator/LlmsDocsGenerator.csproj
Ship pre-generated llmstxt documentation with the server app for online AI consumption and document how to use it.
  • Add static llms*.txt documentation files for categories (table, input, select, button, dialog, nav, card, treeview, form, other), an index, and an example project under wwwroot/llmstxt.
  • Update README with an "For AI Code Agents" section that links to hosted llmstxt endpoints, shows a sample llms.txt for consumers, and documents how to regenerate/check docs via the new tool.
src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-button.txt
src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-card.txt
src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-dialog.txt
src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-example-project.txt
src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-form.txt
src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-input.txt
src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-nav.txt
src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-other.txt
src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-select.txt
src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-table.txt
src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-treeview.txt
src/BootstrapBlazor.Server/wwwroot/llmstxt/llms.txt
README.md
Integrate LLM docs generation and freshness verification into Docker image build and CI workflows.
  • Extend docker GitHub workflow to install .NET 8 SDK and run the LlmsDocsGenerator tool before building/pushing images.
  • Modify server Dockerfile to run LlmsDocsGenerator in a dedicated build stage before building the server project so images contain up-to-date llmstxt files.
  • Update build GitHub workflow to run LlmsDocsGenerator with --check to fail CI when docs are stale relative to component sources.
.github/workflows/docker.yml
src/BootstrapBlazor.Server/Dockerfile
.github/workflows/build.yml
Fix minor typos and documentation comments in existing component source files.
  • Correct a Chinese XML doc comment for ButtonBase.StopPropagation property (点击事件 vs 点击时间).
  • Fix SlideButton Placement property comment to describe the placement of expanded items instead of button color.
  • Correct "DataTime filter component" typo to "DateTime filter component" in DateTimeFilter.
src/BootstrapBlazor/Components/Button/ButtonBase.cs
src/BootstrapBlazor/Components/Button/SlideButton.razor.cs
src/BootstrapBlazor/Components/Filters/DateTimeFilter.razor.cs

Possibly linked issues

  • #0: PR implements and extends the requested LLMs.txt concept via generated llms*.txt docs and integration into build/CI.
  • #0: The PR implements the requested llms.txt-style AI documentation, including generator, files, endpoints, and docs integration.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@bb-auto
Copy link
Copy Markdown

bb-auto Bot commented Jan 1, 2026

Thanks for your PR, @ice6. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@bb-auto bb-auto Bot requested a review from ArgoZhang January 1, 2026 15:21
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 6 issues, and left some high level feedback:

  • The SimplifyTypeName method in ComponentAnalyzer does a series of string replacements (e.g., Nullable< → empty and >?) that will corrupt generic type names (e.g., List<int> or Dictionary<string,int?>), so consider parsing types more structurally (e.g., via Roslyn or a small parser) instead of naïve string replacement to preserve accurate type information in the generated docs.
  • The Dockerfile uses mcr.microsoft.com/dotnet/sdk:10.0 while the CI workflows set up dotnet-version: 8.0.x; it would be safer to align these versions (or parameterize them) so the documentation generator and build run against the same SDK and avoid version-specific inconsistencies.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `SimplifyTypeName` method in `ComponentAnalyzer` does a series of string replacements (e.g., `Nullable<` → empty and `>``?`) that will corrupt generic type names (e.g., `List<int>` or `Dictionary<string,int?>`), so consider parsing types more structurally (e.g., via Roslyn or a small parser) instead of naïve string replacement to preserve accurate type information in the generated docs.
- The Dockerfile uses `mcr.microsoft.com/dotnet/sdk:10.0` while the CI workflows set up `dotnet-version: 8.0.x`; it would be safer to align these versions (or parameterize them) so the documentation generator and build run against the same SDK and avoid version-specific inconsistencies.

## Individual Comments

### Comment 1
<location> `tools/LlmsDocsGenerator/ComponentAnalyzer.cs:315-324` </location>
<code_context>
+        return value;
+    }
+
+    private string SimplifyTypeName(string typeName)
+    {
+        // Simplify common type names
+        return typeName
+            .Replace("System.", "")
+            .Replace("Collections.Generic.", "")
+            .Replace("Threading.Tasks.", "")
+            .Replace("Nullable<", "")
+            .Replace(">", "?")
+            .Replace("Int32", "int")
+            .Replace("Int64", "long")
+            .Replace("Boolean", "bool")
+            .Replace("String", "string")
+            .Replace("Object", "object");
+    }
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Type simplification replaces all '>' with '?' which corrupts most generic type names.

`Replace("Nullable<", "").Replace(">", "?")` will incorrectly rewrite all generic types (e.g. `List<int>``List<int?`, `Task<string>``Task<string?`) by dropping the closing `>` and adding a nullable marker. This produces incorrect signatures in the generated docs.

If the goal is only to map `Nullable<T>` to `T?`, use a more targeted transformation (e.g. regex or explicit parsing of `Nullable<...>`) instead of a global `>` replacement.
</issue_to_address>

### Comment 2
<location> `tools/LlmsDocsGenerator/ComponentAnalyzer.cs:136-143` </location>
<code_context>
+        }
+    }
+
+    private string GetClassName(ClassDeclarationSyntax classDeclaration)
+    {
+        var name = classDeclaration.Identifier.Text;
+
+        // Remove generic part for display name
+        if (classDeclaration.TypeParameterList != null)
+        {
+            return name;
+        }
+
</code_context>

<issue_to_address>
**suggestion:** GetClassName contains an unnecessary conditional and does not match its own comment about generics.

The implementation always returns `name` and never strips the generic arity (e.g. `Table<TItem>`), so it contradicts the comment. The `TypeParameterList` check is therefore dead code. Please either implement the intended normalization (removing the generic suffix) or delete the misleading comment and redundant conditional.

Suggested implementation:

```csharp
    private string GetClassName(ClassDeclarationSyntax classDeclaration)
    {
        return classDeclaration.Identifier.Text;
    }

```

If the existing method body in your file differs slightly (e.g., different spacing or missing the final `return name;`), adjust the SEARCH section to match the exact current implementation of `GetClassName` before applying this replacement.
</issue_to_address>

### Comment 3
<location> `tools/LlmsDocsGenerator/DocsGenerator.cs:124-133` </location>
<code_context>
+    public async Task<bool> CheckAsync()
</code_context>

<issue_to_address>
**suggestion (bug_risk):** CheckAsync validates staleness for category files but not for the main index file.

In `CheckAsync`, you only verify that `llms.txt` exists, whereas you compare timestamps for each category file. As a result, the index can be older than its component files while `--check` still succeeds. Consider adding a last-write comparison for `llms.txt` against all component sources so CI reliably enforces freshness.
</issue_to_address>

### Comment 4
<location> `src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-other.txt:1023` </location>
<code_context>
+
+## DateTimeFilter
+
+DataTime filter component
+
+### Parameters
</code_context>

<issue_to_address>
**issue (typo):** Spelling typo: “DataTime” should be “DateTime”.

Please also update the description to “DateTime filter component” so it matches the component name and .NET type.

```suggestion
DateTime filter component
```
</issue_to_address>

### Comment 5
<location> `src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-button.txt:78` </location>
<code_context>
+| LoadingIcon | `string?` | - | 获得/设置 正在加载动画图标 默认为 fa-solid fa-spin fa-spinner |
+| OnClickWithoutRender | `Func<Task??` | - | 获得/设置 OnClick 事件不刷新父组件 |
+| Size | `Size` | - | 获得/设置 Size 大小 |
+| StopPropagation | `bool` | - | 获得/设置 点击时间是否向上传播 默认 false |
+| Text | `string?` | - | 获得/设置 显示文字 |
+| OnClick | `EventCallback<MouseEventArgs?` | - | 获得/设置 OnClick 事件 |
</code_context>

<issue_to_address>
**issue (typo):** Chinese description likely should say “点击事件” instead of “点击时间”.

Similarly for `StopPropagation`, “点击时间是否向上传播” should be “点击事件是否向上传播” to correctly describe whether the click event is propagated upward.

Suggested implementation:

```
| StopPropagation | `bool` | - | 获得/设置 点击事件是否向上传播 默认 false |

```

在该文件及相关文档中全局搜索“点击时间”,并统一替换为“点击事件”,以确保描述一致且正确。如果有其他属性或事件说明中出现相同的错别字,也需要一并修改。
</issue_to_address>

### Comment 6
<location> `src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-button.txt:421` </location>
<code_context>
+| IsDisabled | `bool` | - | 获得/设置 是否禁用 默认为 false |
+| Items | `IEnumerable<SelectedItem??` | - | 获得/设置 展开项集合 |
+| Offset | `float` | 8 | 获得/设置 弹窗偏移量 默认 8px |
+| Placement | `Placement` | - | 获得/设置 按钮颜色 |
+| ShowHeader | `bool` | - | 获得/设置 是否显示标题 默认 false 不显示 |
+| Size | `Size` | - | 获得/设置 Size 大小 |
</code_context>

<issue_to_address>
**issue (typo):** Description for `Placement` appears to be copied from a color property.

For `SlideButton`, the `Placement` parameter is documented as “获得/设置 按钮颜色”, which doesn’t align with its name or type. Please update the description to match its actual purpose (likely popup/button placement, e.g., slider content position).

```suggestion
| Placement | `Placement` | - | 获得/设置 弹窗/内容显示位置 |
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread tools/LlmsDocsGenerator/ComponentAnalyzer.cs Outdated
Comment thread tools/LlmsDocsGenerator/ComponentAnalyzer.cs Outdated
Comment thread tools/LlmsDocsGenerator/DocsGenerator.cs Outdated
Comment thread src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-other.txt Outdated
Comment thread src/BootstrapBlazor.Server/wwwroot/llms/llms-button.txt Outdated
Comment thread src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-button.txt Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (d1601d6) to head (89f0100).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #7461   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          749       749           
  Lines        32912     32912           
  Branches      4573      4573           
=========================================
  Hits         32912     32912           
Flag Coverage Δ
BB 100.00% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ice6 ice6 marked this pull request as draft January 2, 2026 04:01
@ice6 ice6 closed this Jan 2, 2026
@ice6 ice6 reopened this Jan 2, 2026
@ice6 ice6 marked this pull request as ready for review January 2, 2026 04:31
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 8 issues, and left some high level feedback:

  • In DocsGenerator.CheckAsync you call components.SelectMany(c => c.SourceFiles) but ComponentInfo does not expose a SourceFiles property (only SourcePath/LastModified), so this either won’t compile or is dead code and should be aligned with the current ComponentInfo shape.
  • In ComponentAnalyzer.AnalyzeAllComponentsAsync you analyze both *.razor.cs and *Base.cs and always take the first class in each file, which can lead to duplicate ComponentInfo entries for the same logical component (e.g., Table and TableBase); consider deduplicating by FullName or making the base-class analysis opt-in.
  • The Dockerfile uses mcr.microsoft.com/dotnet/sdk:10.0 while the GitHub workflows install .NET 8.0.x; it would be good to align these versions (or add a comment explaining the intentional difference) to avoid unexpected build/runtime discrepancies.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `DocsGenerator.CheckAsync` you call `components.SelectMany(c => c.SourceFiles)` but `ComponentInfo` does not expose a `SourceFiles` property (only `SourcePath`/`LastModified`), so this either won’t compile or is dead code and should be aligned with the current `ComponentInfo` shape.
- In `ComponentAnalyzer.AnalyzeAllComponentsAsync` you analyze both `*.razor.cs` and `*Base.cs` and always take the first class in each file, which can lead to duplicate `ComponentInfo` entries for the same logical component (e.g., `Table` and `TableBase`); consider deduplicating by `FullName` or making the base-class analysis opt-in.
- The Dockerfile uses `mcr.microsoft.com/dotnet/sdk:10.0` while the GitHub workflows install `.NET 8.0.x`; it would be good to align these versions (or add a comment explaining the intentional difference) to avoid unexpected build/runtime discrepancies.

## Individual Comments

### Comment 1
<location> `tools/LlmsDocsGenerator/DocsGenerator.cs:142-143` </location>
<code_context>
+        var indexLastWrite = File.GetLastWriteTimeUtc(indexPath);
+
+        // compute the most recent component source timestamp:
+        var newestComponentWrite = components
+            .SelectMany(c => c.SourceFiles)
+            .Select(File.GetLastWriteTimeUtc)
+            .DefaultIfEmpty(indexLastWrite)
</code_context>

<issue_to_address>
**issue (bug_risk):** DocsGenerator.CheckAsync references a non-existent ComponentInfo.SourceFiles property, which will fail to compile.

`ComponentInfo` in this change set only exposes `SourcePath` and `LastModified`, so `components.SelectMany(c => c.SourceFiles)` will not compile. Either add and populate a `SourceFiles` collection on `ComponentInfo` (via `ComponentAnalyzer`), or rework this freshness check to rely on existing properties (e.g., `components.Max(c => c.LastModified)`).
</issue_to_address>

### Comment 2
<location> `tools/LlmsDocsGenerator/DocsGenerator.cs:96-98` </location>
<code_context>
+
+    private async Task GenerateIndexAsync(Dictionary<string, List<ComponentInfo>> categorized)
+    {
+        var indexPath = Path.Combine(_outputPath, "llms.txt");
+        var content = _markdownBuilder.BuildIndex(categorized);
+        await File.WriteAllTextAsync(indexPath, content);
+        Console.WriteLine($"Generated: {indexPath}");
+    }
</code_context>

<issue_to_address>
**issue:** Output directory is not ensured before writing files, which may fail if the directory does not exist.

If `_outputPath` (or its parent dirs) don’t exist, `File.WriteAllTextAsync` will throw—likely in fresh clones or CI. Please ensure the directory is created once (e.g., in the `DocsGenerator` ctor or before the first write) with `Directory.CreateDirectory(_outputPath);`, and apply the same handling to the other write locations (category and per-component docs).
</issue_to_address>

### Comment 3
<location> `tools/LlmsDocsGenerator/MarkdownBuilder.cs:248-257` </location>
<code_context>
+            _sb.AppendLine();
+        }
+
+        // Parameters table
+        if (component.Parameters.Count > 0)
+        {
+            _sb.AppendLine("### Parameters");
+            _sb.AppendLine();
+            _sb.AppendLine("<!-- AUTO-GENERATED-PARAMETERS-START -->");
+            _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)
</code_context>

<issue_to_address>
**suggestion:** EventCallback parameters are listed in both the general parameter table and the dedicated Event Callbacks table.

Since you already render a dedicated `Event Callbacks` section from `component.Parameters.Where(p => p.IsEventCallback)`, including those items in the main `Parameters` table duplicates information and adds noise. Consider filtering out `IsEventCallback` entries from the main table so callbacks are documented only in their own section.

Suggested implementation:

```csharp
        // Parameters table (excluding EventCallback parameters; those are documented separately)
        var nonEventParams = component.Parameters
            .Where(p => !p.IsEventCallback)
            .ToList();

        if (nonEventParams.Count > 0)
        {
            _sb.AppendLine("### Parameters");
            _sb.AppendLine();
            _sb.AppendLine("<!-- AUTO-GENERATED-PARAMETERS-START -->");
            _sb.AppendLine();
            _sb.AppendLine("| Parameter | Type | Default | Description |");
            _sb.AppendLine("|-----------|------|---------|-------------|");

            // Sort: required first, then alphabetically
            var sortedParams = nonEventParams
                .OrderByDescending(p => p.IsRequired)
                .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} |");

```

1. Ensure `using System.Linq;` is present at the top of `MarkdownBuilder.cs` so that `Where`, `OrderByDescending`, and `ThenBy` compile correctly.
2. No other changes should be required, assuming the existing Event Callbacks section already uses `component.Parameters.Where(p => p.IsEventCallback)` as mentioned in your comment.
</issue_to_address>

### Comment 4
<location> `src/BootstrapBlazor.Server/Dockerfile:19-20` </location>
<code_context>

-WORKDIR "src/BootstrapBlazor.Server"
+# Generate LLM documentation
+WORKDIR /tools/LlmsDocsGenerator
+RUN dotnet run
+
+WORKDIR "/src/BootstrapBlazor.Server"
</code_context>

<issue_to_address>
**issue (bug_risk):** In the Docker build, the generators default output path likely doesnt align with the desired wwwroot/llmstxt folder.

Because `Program.cs` resolves the default `--output` relative to the current directory, running `dotnet run` from `/tools/LlmsDocsGenerator` writes to `/tools/LlmsDocsGenerator/src/BootstrapBlazor.Server/wwwroot/llmstxt`, not `/src/BootstrapBlazor.Server/wwwroot/llmstxt` where the app serves static files. To align paths, either invoke the tool from the repo root (e.g., `WORKDIR /` then `dotnet run --project tools/LlmsDocsGenerator`) or pass `--output /src/BootstrapBlazor.Server/wwwroot/llmstxt` explicitly.
</issue_to_address>

### Comment 5
<location> `src/BootstrapBlazor.Server/Dockerfile:18-20` </location>
<code_context>
-WORKDIR "src/BootstrapBlazor.Server"
+# Generate LLM documentation
+WORKDIR /tools/LlmsDocsGenerator
+RUN dotnet run
+
+WORKDIR "/src/BootstrapBlazor.Server"
</code_context>

<issue_to_address>
**suggestion (performance):** Using plain `dotnet run` in the Docker build stage may slow builds due to implicit restore/build in Debug configuration.

Here `dotnet run` triggers an extra restore/build (usually Debug) on top of the later `dotnet build/publish`, which slows image builds and adds unnecessary artifacts to this layer. Prefer either `dotnet build` followed by `dotnet run --configuration Release --no-build`, or publishing the tool once and running the published DLL to avoid duplicate compilation.

```suggestion
# Generate LLM documentation
WORKDIR /tools/LlmsDocsGenerator
# Build once in Release and run without rebuilding to avoid duplicate restore/build work
RUN dotnet build --configuration Release
RUN dotnet run --configuration Release --no-build
```
</issue_to_address>

### Comment 6
<location> `src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-other.txt:286` </location>
<code_context>
+| CanPreviewCallback | `Func<UploadFile, bool>?` | - | 获得/设置 是否允许预览回调方法 默认 null |
+| DeleteIcon | `string?` | - | 获得/设置 删除图标 |
+| Height | `int` | 100 | 获得/设置 文件预览框高度 |
+| InvalidStatusIcon | `string?` | - | 获得/设置 状态正常图标 |
+| IsCircle | `bool` | - | 获得/设置 是否圆形图片框 Avatar 模式时生效 默认为 false |
+| IsUploadButtonAtFirst | `bool` | - | 获得/设置 继续上传按钮是否在列表前 默认 false |
</code_context>

<issue_to_address>
**issue (typo):** The `InvalidStatusIcon` description appears to be a copy-paste error.

For `AvatarUpload`, the `InvalidStatusIcon` (“无效状态图标”) description currently says “状态正常图标”, which contradicts the property name. Please update it to reflect the invalid state (for example: “获得/设置 状态无效图标”) and align with other `InvalidStatusIcon` descriptions.

```suggestion
| InvalidStatusIcon | `string?` | - | 获得/设置 状态无效图标 |
```
</issue_to_address>

### Comment 7
<location> `src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-other.txt:951-952` </location>
<code_context>
+| FirstDayOfWeek | `DayOfWeek` | DayOfWeek.Sunday | 获得/设置 星期第一天 默认 <see cref="DayOfWeek.Sunday"/> |
+| MaxValue | `DateTime?` | - | 获得/设置 当前日期最大值 |
+| MinValue | `DateTime?` | - | 获得/设置 当前日期最小值 |
+| NextMonthIcon | `string?` | - | 获得/设置 上一年图标 |
+| NextYearIcon | `string?` | - | 获得/设置 上一年图标 |
+| NowButtonText | `string?` | - | 获得/设置 此刻按钮文字 |
+| OnClear | `Func<Task>?` | - | 获得/设置 清空按钮回调委托 |
</code_context>

<issue_to_address>
**issue (typo):** `NextMonthIcon` and `NextYearIcon` descriptions reference the wrong period (previous year).

Recommend updating the Chinese documentation to describe these as the icons for the next month/year (e.g., “获得/设置 下一月图标” and “获得/设置 下一年图标”) to stay consistent with the `Next*` naming and avoid confusion with the `Previous*` icons.

Suggested implementation:

```
| NextMonthIcon | `string?` | - | 获得/设置 下一月图标 |

```

```
| NextYearIcon | `string?` | - | 获得/设置 下一年图标 |

```
</issue_to_address>

### Comment 8
<location> `src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-other.txt:1175` </location>
<code_context>
+| LookupService | `ILookupService?` | - | <inheritdoc/> |
+| LookupServiceData | `object?` | - | <inheritdoc/> |
+| LookupServiceKey | `string?` | - | <inheritdoc/> |
+| LookupStringComparison | `stringComparison` | StringComparison.OrdinalIgnoreCase | <inheritdoc/> |
+| ShowTooltip | `bool` | - | 获得/设置 是否显示 Tooltip 多用于标签文字过长导致裁减时使用 默认 false 不显示 |
+| TypeResolver | `Func<Assembly?, string, bool, Type?>?` | - | 获得/设置 类型解析回调方法 组件泛型为 Array 时内部调用 |
</code_context>

<issue_to_address>
**issue (typo):** The `StringComparison` type is misspelled with a lowercase `s` in multiple parameter tables.

This is written as `stringComparison`, but the correct .NET type is `StringComparison` (capital `S`). The same issue appears in other parameter tables (e.g., `MultiFilter.StringComparison`, `SelectBase.StringComparison`, and `llms-select.txt` for `Select.StringComparison`). Please update all occurrences for accuracy and consistency.

Suggested implementation:

```
| LookupStringComparison | `StringComparison` | StringComparison.OrdinalIgnoreCase | <inheritdoc/> |

```

Based on your review comment, similar fixes are needed in other parameter tables:

1. In the documentation entries for:
   - `MultiFilter.StringComparison`
   - `SelectBase.StringComparison`
   ensure the Type column uses ``StringComparison`` (capital S) instead of ``stringComparison``.

2. In `src/BootstrapBlazor.Server/wwwroot/llmstxt/llms-select.txt`, locate the row for `Select.StringComparison` and change the Type column from ``stringComparison`` to ``StringComparison``.

The pattern to search for in those files is typically a table row like:
`| *StringComparison* | \`stringComparison\` | ... | ... |`
which should be updated to:
`| *StringComparison* | \`StringComparison\` | ... | ... |`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread tools/LlmsDocsGenerator/DocsGenerator.cs Outdated
Comment thread tools/LlmsDocsGenerator/DocsGenerator.cs Outdated
Comment thread tools/LlmsDocsGenerator/MarkdownBuilder.cs Outdated
Comment thread src/BootstrapBlazor.Server/Dockerfile Outdated
Comment thread src/BootstrapBlazor.Server/Dockerfile Outdated
Comment thread src/BootstrapBlazor.Server/wwwroot/llms/llms-other.txt Outdated
@ice6 ice6 marked this pull request as draft January 2, 2026 04:57
@bb-auto bb-auto Bot added the enhancement New feature or request label Jan 2, 2026
@bb-auto bb-auto Bot added this to the v10.1.0 milestone Jan 2, 2026
@ArgoZhang ArgoZhang changed the title feat(llmstxt): llms poc feat(llms): add cli tools for llms Jan 2, 2026
@ArgoZhang ArgoZhang marked this pull request as ready for review January 3, 2026 10:32
Copilot AI review requested due to automatic review settings January 3, 2026 10:32
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We encountered an error and are unable to review this PR. We have been notified and are working to fix it.

You can try again by commenting this pull request with @sourcery-ai review, or contact us for help.

@ArgoZhang ArgoZhang merged commit f71821f into dotnetcore:main Jan 3, 2026
9 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request introduces an automated LLM documentation generation system for BootstrapBlazor components. The system uses a new CLI tool (bootstrapblazor.llmsdocsgenerator) to analyze components and generate LLM-friendly Markdown documentation that can be consumed by AI code assistants.

Key Changes:

  • Added a new .NET tool (bootstrapblazor.llmsdocsgenerator v10.0.0) for generating LLM documentation
  • Integrated documentation generation into the build process via MSBuild targets
  • Configured git to ignore generated documentation and code agent directories

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/BootstrapBlazor.Server/Directory.Build.targets Adds MSBuild target that runs LLM docs generator after every build
dotnet-tools.json Registers the new bootstrapblazor.llmsdocsgenerator tool (v10.0.0)
.gitignore Excludes generated LLM documentation files and code agent directories from version control
exclusion.dic Adds "llms" to the spell-check exclusion dictionary

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -0,0 +1,9 @@
<Project>

<Target Name="LLMs" AfterTargets="Build" Condition="'$(TargetFramework)' == '$(RunTargetFramework)'">
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LLMs target runs after every build when the TargetFramework matches RunTargetFramework. This means the documentation generation will execute on every build, even for incremental builds during development. Consider adding a condition to skip this in development environments or making it opt-in via a build property (e.g., Condition="'$(GenerateLlmsDocs)' == 'true'"), to avoid impacting build performance during routine development.

Suggested change
<Target Name="LLMs" AfterTargets="Build" Condition="'$(TargetFramework)' == '$(RunTargetFramework)'">
<Target Name="LLMs" AfterTargets="Build" Condition="'$(TargetFramework)' == '$(RunTargetFramework)' and '$(GenerateLlmsDocs)' == 'true'">

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +5
<Target Name="LLMs" AfterTargets="Build" Condition="'$(TargetFramework)' == '$(RunTargetFramework)'">
<Message Text="LLMs documentation generating ..." Importance="high"></Message>
<Exec Command="dotnet tool restore"></Exec>
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running "dotnet tool restore" on every build can slow down the build process unnecessarily. Consider checking if the tool is already available before restoring, or move the tool restore to a separate target that only runs when needed. Alternatively, document that developers should run "dotnet tool restore" manually before building.

Suggested change
<Target Name="LLMs" AfterTargets="Build" Condition="'$(TargetFramework)' == '$(RunTargetFramework)'">
<Message Text="LLMs documentation generating ..." Importance="high"></Message>
<Exec Command="dotnet tool restore"></Exec>
<PropertyGroup>
<!-- Set to true to restore local .NET tools explicitly when needed -->
<RestoreDotNetTools>false</RestoreDotNetTools>
</PropertyGroup>
<Target Name="RestoreDotNetToolsTarget" Condition="'$(RestoreDotNetTools)' == 'true'">
<Exec Command="dotnet tool restore"></Exec>
</Target>
<Target Name="LLMs" AfterTargets="Build" Condition="'$(TargetFramework)' == '$(RunTargetFramework)'">
<Message Text="LLMs documentation generating ..." Importance="high"></Message>

Copilot uses AI. Check for mistakes.
<Target Name="LLMs" AfterTargets="Build" Condition="'$(TargetFramework)' == '$(RunTargetFramework)'">
<Message Text="LLMs documentation generating ..." Importance="high"></Message>
<Exec Command="dotnet tool restore"></Exec>
<Exec Command="dotnet llms-docs --root=$(MSBuildThisFileDirectory) --debug"></Exec>
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --debug flag appears to be included in the production build target. Consider making this conditional based on the build configuration (e.g., only include --debug when Configuration is Debug) to avoid unnecessary debug output in release builds.

Suggested change
<Exec Command="dotnet llms-docs --root=$(MSBuildThisFileDirectory) --debug"></Exec>
<Exec Command="dotnet llms-docs --root=$(MSBuildThisFileDirectory) --debug" Condition="'$(Configuration)' == 'Debug'"></Exec>
<Exec Command="dotnet llms-docs --root=$(MSBuildThisFileDirectory)" Condition="'$(Configuration)' != 'Debug'"></Exec>

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(llms.txt): just like https://ant.design/llms.txt

3 participants