dotnet-tooling Overview .NET project setup, build systems, performance, CLI apps, and developer tooling. This consolidated skill spans 34 topic areas. Load the appropriate companion file from based on the routing table below. Routing Table | Topic | Keywords | Description | Companion File | |-------|----------|-------------|----------------| | Project structure | solution, .slnx, CPM, analyzers | .slnx, Directory.Build.props, CPM, analyzers | references/project-structure.md | | Scaffold project | dotnet new, CPM, SourceLink, editorconfig | dotnet new with CPM, analyzers, editorconfig, SourceL…

\n token: ${{ secrets.WINGET_GITHUB_TOKEN }}\n```\n\n### Scoop Manifest Update\n\n```yaml\n update-scoop:\n needs: release\n if: ${{ !contains(github.ref_name, '-') }}\n runs-on: ubuntu-latest\n steps:\n - name: Extract version\n id: version\n run: echo \"version=${GITHUB_REF_NAME#v}\" >> \"$GITHUB_OUTPUT\"\n\n - uses: actions/checkout@v4\n with:\n repository: myorg/scoop-mytool\n token: ${{ secrets.SCOOP_GITHUB_TOKEN }}\n\n - name: Download checksums\n run: |\n set -euo pipefail\n curl -sL \"https://github.com/myorg/mytool/releases/download/v${{ steps.version.outputs.version }}/checksums-sha256.txt\" \\\n -o checksums.txt\n\n - name: Update manifest\n run: |\n set -euo pipefail\n VERSION=\"${{ steps.version.outputs.version }}\"\n WIN_X64_SHA=$(grep \"win-x64\" checksums.txt | awk '{print $1}')\n\n # Update bucket/mytool.json with new version and hash\n jq --arg v \"$VERSION\" --arg h \"$WIN_X64_SHA\" \\\n '.version = $v | .architecture.\"64bit\".hash = $h |\n .architecture.\"64bit\".url = \"https://github.com/myorg/mytool/releases/download/v\\($v)/mytool-\\($v)-win-x64.zip\"' \\\n bucket/mytool.json > tmp.json && mv tmp.json bucket/mytool.json\n\n - name: Create PR\n uses: peter-evans/create-pull-request@v6\n with:\n title: \"mytool ${{ steps.version.outputs.version }}\"\n commit-message: \"Update mytool to ${{ steps.version.outputs.version }}\"\n branch: \"update-mytool-${{ steps.version.outputs.version }}\"\n```\n\n\n## Versioning Strategy Details\n\n### SemVer for CLI Tools\n\n| Change Type | Version Bump | Example |\n|-------------|-------------|---------|\n| Breaking CLI flag rename/removal | Major | 1.x.x -> 2.0.0 |\n| New command or option | Minor | x.1.x -> x.2.0 |\n| Bug fix, performance improvement | Patch | x.x.1 -> x.x.2 |\n| Release candidate | Pre-release suffix | x.x.x-rc.1 |\n\n### Version Embedding\n\nThe version flows from the git tag through `dotnet publish` into the binary:\n\n```xml\n\u003c!-- .csproj -- Version is set at publish time via /p:Version -->\n\u003cPropertyGroup>\n \u003c!-- Fallback version for local development -->\n \u003cVersion>0.0.0-dev\u003c/Version>\n\u003c/PropertyGroup>\n```\n\n```bash\n# --version output matches the git tag\n$ mytool --version\n1.2.3\n```\n\n### Tagging Workflow\n\n```bash\n# 1. Update CHANGELOG.md (if applicable)\n# 2. Commit the changelog\ngit commit -am \"docs: update changelog for v1.2.3\"\n\n# 3. Tag the release\ngit tag -a v1.2.3 -m \"Release v1.2.3\"\n\n# 4. Push tag -- triggers the release workflow\ngit push origin v1.2.3\n```\n\n\n## Workflow Security\n\n### Secret Management\n\n```yaml\n# Required repository secrets:\n# NUGET_API_KEY - NuGet.org API key for package publishing\n# TAP_GITHUB_TOKEN - PAT with repo scope for homebrew-tap\n# WINGET_GITHUB_TOKEN - PAT with public_repo scope for winget-pkgs PRs\n# SCOOP_GITHUB_TOKEN - PAT with repo scope for scoop bucket\n# CHOCO_API_KEY - Chocolatey API key for package push\n```\n\n### Permissions\n\n```yaml\npermissions:\n contents: write # Minimum: create GitHub Releases and upload assets\n```\n\nUse job-level permissions when different jobs need different scopes. Never grant `write-all`.\n\n\n## Agent Gotchas\n\n1. **Do not use `set -e` without `set -o pipefail` in GitHub Actions bash steps.** Without `pipefail`, a failing command piped to `tee` or another utility exits 0, masking the failure. Always use `set -euo pipefail`.\n2. **Do not hardcode the .NET version in the publish path.** Use `dotnet publish -o ./publish` to control the output directory explicitly. Hardcoding `net8.0` in artifact paths breaks when upgrading to .NET 9+.\n3. **Do not skip the pre-release detection step.** Package manager submissions (Homebrew, winget, Scoop, Chocolatey, NuGet) must be gated on stable versions. Publishing a `-rc.1` to winget-pkgs or NuGet as stable causes user confusion.\n4. **Do not use `actions/upload-artifact` v3 with `merge-multiple`.** The `merge-multiple` parameter requires `actions/download-artifact@v4`. Using v3 silently ignores the flag and creates nested directories.\n5. **Do not forget `retention-days: 1` on intermediate build artifacts.** Release artifacts are published to GitHub Releases (permanent). Workflow artifacts are temporary and should expire quickly to save storage.\n6. **Do not create GitHub Releases with `gh release create` in a matrix job.** Only the release job (after all builds complete) should create the release. Matrix jobs upload artifacts; the release job assembles them.\n\n\n## References\n\n- [GitHub Actions workflow syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions)\n- [softprops/action-gh-release](https://github.com/softprops/action-gh-release)\n- [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request)\n- [vedantmgoyal9/winget-releaser](https://github.com/vedantmgoyal9/winget-releaser)\n- [Semantic Versioning](https://semver.org/)\n- [.NET versioning](https://learn.microsoft.com/en-us/dotnet/core/versions/)\n- [GitHub Actions artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":16597,"content_sha256":"d6492c582ae81ab40abb9eed9b8ed2a7682fd1594da285b0f69f5fd89fda3f58"},{"filename":"references/csharp-lsp.md","content":"# C# Language Server Protocol (LSP)\n\nC# LSP servers provide code intelligence — go to definition, find references, hover info, symbol search, and call hierarchies — that agents can use to navigate and understand .NET codebases. This reference covers available C# LSP servers, how to install and configure them, and standard LSP operations agents should use for .NET code navigation.\n\n## Why Agents Should Use LSP\n\nSearching codebases with text patterns (`grep`, `ripgrep`) finds string matches but misses semantic meaning. LSP provides **semantic code navigation** that understands:\n\n- Where a symbol is defined (not just where the string appears)\n- All references to a symbol across the entire solution\n- The full type signature and documentation of a symbol\n- Interface implementations and virtual method overrides\n- Call hierarchies (who calls this method, what does this method call)\n\n**Use LSP when:**\n- Navigating unfamiliar .NET codebases\n- Finding all usages of a type, method, or property before refactoring\n- Understanding the type of a variable or return value\n- Tracing call chains through multiple layers\n- Finding implementations of an interface\n\n**Use text search (grep/ripgrep) when:**\n- Searching for string literals, comments, or configuration values\n- Finding files by name pattern\n- Searching across non-C# files (YAML, JSON, XML, Markdown)\n- Quick keyword searches where semantic precision isn't needed\n\n## C# LSP Servers\n\n### csharp-ls (Recommended for Agents)\n\nLightweight, open-source C# language server. Fast startup, low memory, works in any editor or agent runtime that supports LSP.\n\n```bash\n# Install as a global .NET tool\ndotnet tool install --global csharp-ls\n\n# Run (listens on stdin/stdout by default)\ncsharp-ls\n\n# Or specify a solution\ncsharp-ls --solution ./MyApp.sln\n```\n\n**Capabilities:** Go to definition, find references, hover, document/workspace symbols, rename, code actions, diagnostics. Does not support call hierarchy (limited by Roslyn APIs available to it).\n\n**Best for:** CLI-based agents, headless environments, CI pipelines, lightweight setups.\n\n### OmniSharp (Feature-Rich)\n\nFull-featured C# language server built on Roslyn. Heavier than csharp-ls but supports more features.\n\n```bash\n# Install via .NET tool\ndotnet tool install --global omnisharp\n\n# Or download standalone binary\n# https://github.com/OmniSharp/omnisharp-roslyn/releases\n```\n\n**Capabilities:** Full Roslyn-powered analysis including call hierarchy, refactoring, code fixes, analyzers. Higher memory usage.\n\n**Best for:** Feature-complete environments, when call hierarchy is needed.\n\n### C# Dev Kit LSP (VS Code Only)\n\nMicrosoft's proprietary LSP bundled with the C# Dev Kit VS Code extension. Not available outside VS Code.\n\n**Best for:** Human developers using VS Code. Not usable by agents in headless or non-VS Code environments.\n\n### Choosing a Server\n\n| Server | Install | Memory | Call Hierarchy | Headless | License |\n|--------|---------|--------|----------------|----------|---------|\n| csharp-ls | `dotnet tool install -g csharp-ls` | Low | No | Yes | MIT |\n| OmniSharp | `dotnet tool install -g omnisharp` | High | Yes | Yes | MIT |\n| C# Dev Kit | VS Code extension | Medium | Yes | No | Proprietary |\n\n**Recommendation:** Use `csharp-ls` for agent scenarios. It starts fast, uses less memory, and covers the operations agents need most (definition, references, hover, symbols).\n\n## LSP Operations for .NET Navigation\n\nThe standard LSP protocol defines operations that map directly to .NET code navigation tasks. Agent runtimes expose these differently (e.g., Claude Code has an `LSP` tool, Codex uses internal APIs), but the concepts are universal.\n\n### Go to Definition\n\nFind where a symbol (type, method, property, field) is defined.\n\n**Use when:** You see a type or method used in code and need to understand its implementation.\n\n```\nOperation: textDocument/definition\nInput: file path + position (line, character) on the symbol\nOutput: file path + position of the definition\n```\n\n**Example scenarios:**\n- Cursor on `IOrderRepository` → jumps to the interface definition\n- Cursor on `CreateAsync` method call → jumps to the method declaration\n- Cursor on a type in a `using` directive → jumps to the type definition\n\n### Find References\n\nFind all locations where a symbol is used across the workspace.\n\n**Use when:** Before renaming, refactoring, or deleting a symbol. Also useful for understanding how widely a type/method is used.\n\n```\nOperation: textDocument/references\nInput: file path + position on the symbol\nOutput: list of all locations (file + position) where the symbol is referenced\n```\n\n**Example scenarios:**\n- Find all callers of `OrderService.CreateAsync`\n- Find all classes that implement `IRepository\u003cT>`\n- Find all places that read or write a property\n\n### Hover\n\nGet type information, documentation, and signatures for a symbol.\n\n**Use when:** You need to know the type of a variable, the signature of a method, or the XML doc comment for a symbol.\n\n```\nOperation: textDocument/hover\nInput: file path + position on the symbol\nOutput: type signature, documentation, parameter info\n```\n\n**Example scenarios:**\n- Hover over `var order` → reveals `Order` type\n- Hover over a method → shows full signature with parameter types and return type\n- Hover over a generic type → shows resolved type parameters\n\n### Document Symbols\n\nList all symbols (classes, methods, properties, fields) in a file.\n\n**Use when:** Getting an overview of a file's structure without reading every line.\n\n```\nOperation: textDocument/documentSymbol\nInput: file path\nOutput: hierarchical list of symbols with their kinds and positions\n```\n\n### Workspace Symbol Search\n\nSearch for symbols by name across the entire workspace/solution.\n\n**Use when:** Looking for a class, interface, or method by name across a large codebase.\n\n```\nOperation: workspace/symbol\nInput: search query string\nOutput: list of matching symbols with file paths and positions\n```\n\n**Example scenarios:**\n- Search for `OrderService` → finds the class definition\n- Search for `IRepository` → finds the interface and all related types\n- Search for `Configure` → finds all Configure methods across the solution\n\n### Go to Implementation\n\nFind concrete implementations of an interface or abstract method.\n\n**Use when:** You see an interface type and need to find the classes that implement it.\n\n```\nOperation: textDocument/implementation\nInput: file path + position on an interface or abstract member\nOutput: list of implementing types/methods\n```\n\n**Example scenarios:**\n- Cursor on `IOrderRepository` → finds `SqlOrderRepository`, `InMemoryOrderRepository`\n- Cursor on abstract `ProcessAsync` → finds all override implementations\n\n### Call Hierarchy\n\nTrace incoming calls (who calls this) and outgoing calls (what does this call).\n\n**Use when:** Understanding the flow of execution through a codebase. Requires OmniSharp or C# Dev Kit (csharp-ls does not support this).\n\n```\nOperation: callHierarchy/incomingCalls\nInput: file path + position on a method\nOutput: list of methods that call this method\n\nOperation: callHierarchy/outgoingCalls\nInput: file path + position on a method\nOutput: list of methods called by this method\n```\n\n## Agent Navigation Patterns\n\n### Pattern 1: Understand a Type\n\n1. **Workspace symbol search** for the type name\n2. **Go to definition** to read the type\n3. **Document symbols** to see all members\n4. **Find references** on key methods to understand usage patterns\n\n### Pattern 2: Trace a Feature\n\n1. **Find the entry point** (API endpoint, command handler, event handler)\n2. **Go to definition** on each dependency/service used\n3. **Go to implementation** on interfaces to find concrete classes\n4. **Outgoing calls** to trace the execution chain\n\n### Pattern 3: Prepare for Refactoring\n\n1. **Find references** on the symbol being refactored\n2. **Go to implementation** to find all concrete implementations\n3. **Incoming calls** to understand who depends on this\n4. Review each reference location before making changes\n\n### Pattern 4: Explore Unfamiliar Codebase\n\n1. **Document symbols** on `Program.cs` or `Startup.cs` to find entry points\n2. **Workspace symbol search** for key domain types (e.g., `Order`, `Customer`)\n3. **Go to definition** on DI registrations to find service implementations\n4. **Hover** on `var` declarations to reveal types\n\n## Configuration\n\n### Editor-Agnostic LSP Client Configuration\n\nMost LSP clients can be configured to use any C# server. The server communicates via stdin/stdout using the LSP JSON-RPC protocol.\n\n```json\n// Generic LSP client configuration\n{\n \"languageId\": \"csharp\",\n \"command\": \"csharp-ls\",\n \"args\": [\"--solution\", \"./MyApp.sln\"],\n \"rootUri\": \"file:///path/to/project\"\n}\n```\n\n### VS Code settings.json\n\n```json\n{\n \"omnisharp.path\": \"latest\",\n \"omnisharp.enableRoslynAnalyzers\": true,\n \"omnisharp.enableEditorConfigSupport\": true\n}\n```\n\n### Neovim (nvim-lspconfig)\n\n```lua\nrequire('lspconfig').csharp_ls.setup({\n cmd = { \"csharp-ls\" },\n root_dir = function(fname)\n return require('lspconfig.util').root_pattern(\"*.sln\", \"*.csproj\")(fname)\n end\n})\n```\n\n## Agent Gotchas\n\n1. **LSP requires a running server** — agents cannot use LSP operations without first ensuring a C# language server is available. Check if one is running or start one.\n2. **Solution context matters** — LSP servers resolve symbols relative to a solution or project. If the server is started without `--solution`, it may not find cross-project references.\n3. **LSP positions are typically 0-based** — the protocol uses 0-based line and character offsets, but some agent runtimes (like Claude Code) use 1-based positions. Check the agent runtime's documentation.\n4. **Go to definition on NuGet types goes to metadata** — you'll see decompiled source or metadata signatures, not editable source. Use ILSpy/ilspycmd for full decompilation (see `references/ilspy-decompile.md`).\n5. **LSP is slower than text search for simple queries** — don't use LSP when `grep` for a string literal would suffice. Reserve LSP for semantic navigation.\n6. **The server needs time to initialize** — on first start, the C# LSP loads the solution and builds a semantic model. Large solutions may take 10-30 seconds. Subsequent operations are fast.\n7. **csharp-ls is preferred over OmniSharp for headless agents** — lower memory footprint, faster startup, and sufficient for most navigation tasks.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10486,"content_sha256":"6d20623f952abf89a68194125c8abeacc44c102b1738cabc4f957c30c384e337"},{"filename":"references/csproj-reading.md","content":"# Csproj Reading\n\nTeaches agents to read and safely modify SDK-style .csproj files. Covers project structure, PropertyGroup conventions, ItemGroup patterns, conditional expressions, Directory.Build.props/.targets, and central package management (Directory.Packages.props). Each subsection provides annotated XML examples and common modification patterns.\n\n## Prerequisites\n\n.NET 8.0+ SDK. SDK-style projects only (legacy .csproj format is not covered). MSBuild (included with .NET SDK).\n\nCross-references: `references/project-structure.md` for project organization and SDK selection, `references/build-analysis.md` for interpreting build errors from project misconfiguration, [skill:dotnet-api] for common project structure mistakes agents make.\n\n\n## Subsection 1: SDK-Style Project Structure\n\nSDK-style projects use a `\u003cProject Sdk=\"...\">` declaration that imports hundreds of default targets and props. Understanding what the SDK provides implicitly is essential to avoid redundant or conflicting declarations.\n\n### Annotated XML Example\n\n```xml\n\u003c!-- The Sdk attribute imports default props at the top and targets at the bottom -->\n\u003c!-- This single line replaces dozens of Import statements from legacy .csproj -->\n\u003cProject Sdk=\"Microsoft.NET.Sdk\">\n \u003c!--\n Common SDK values:\n - Microsoft.NET.Sdk -> Console apps, libraries, class libraries\n - Microsoft.NET.Sdk.Web -> ASP.NET Core (adds Kestrel, MVC, Razor, shared framework)\n - Microsoft.NET.Sdk.Worker -> Background worker services\n - Microsoft.NET.Sdk.Razor -> Razor class libraries\n - Microsoft.NET.Sdk.BlazorWebAssembly -> Blazor WASM apps\n -->\n\n \u003c!-- SDK-style projects auto-include all *.cs files via default globs -->\n \u003c!-- No need to list individual .cs files in \u003cCompile Include=\"...\"> -->\n \u003c!-- Default globs: **/*.cs for Compile, **/*.resx for EmbeddedResource -->\n\n \u003cPropertyGroup>\n \u003cTargetFramework>net9.0\u003c/TargetFramework>\n \u003c/PropertyGroup>\n\n\u003c/Project>\n```\n\n### Common Modification Patterns\n\n**Changing SDK type** -- when an agent creates a web project with the wrong SDK:\n\n```xml\n\u003c!-- WRONG: console SDK for a web project -->\n\u003cProject Sdk=\"Microsoft.NET.Sdk\">\n\n\u003c!-- CORRECT: Web SDK includes ASP.NET Core shared framework -->\n\u003cProject Sdk=\"Microsoft.NET.Sdk.Web\">\n```\n\n**Disabling default globs** -- rare, but needed when migrating from legacy format or when explicit file control is required:\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- Disable automatic inclusion of *.cs files -->\n \u003cEnableDefaultCompileItems>false\u003c/EnableDefaultCompileItems>\n \u003c!-- Disable all default items (Compile, EmbeddedResource, Content) -->\n \u003cEnableDefaultItems>false\u003c/EnableDefaultItems>\n\u003c/PropertyGroup>\n```\n\n**Verifying which SDK a project uses:**\n\n```bash\n# Check the first line of the .csproj for the Sdk attribute\nhead -1 src/MyApp/MyApp.csproj\n# Output: \u003cProject Sdk=\"Microsoft.NET.Sdk.Web\">\n```\n\n\n## Subsection 2: PropertyGroup Conventions\n\nPropertyGroup elements contain scalar MSBuild properties. The most important properties control the target framework, language features, and output type.\n\n### Annotated XML Example\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- Target Framework Moniker (TFM) -- determines runtime and API surface -->\n \u003c!-- Use the latest LTS or STS release; prefer the repo's existing TFM. -->\n \u003cTargetFramework>net9.0\u003c/TargetFramework>\n\n \u003c!-- For multi-targeting, use plural form (see Subsection 4) -->\n \u003c!-- \u003cTargetFrameworks>net8.0;net9.0\u003c/TargetFrameworks> -->\n\n \u003c!-- Enable nullable reference types (recommended for all new projects) -->\n \u003cNullable>enable\u003c/Nullable>\n\n \u003c!-- Enable implicit global usings (System, System.Linq, etc.) -->\n \u003cImplicitUsings>enable\u003c/ImplicitUsings>\n\n \u003c!-- Output type: Exe for apps, omit or Library for libraries -->\n \u003cOutputType>Exe\u003c/OutputType>\n \u003c!-- Omitting OutputType defaults to Library (produces .dll) -->\n\n \u003c!-- Root namespace -- defaults to project name if omitted -->\n \u003cRootNamespace>MyApp.Api\u003c/RootNamespace>\n\n \u003c!-- Assembly name -- defaults to project name if omitted -->\n \u003cAssemblyName>MyApp.Api\u003c/AssemblyName>\n\n \u003c!-- Language version -- usually omitted (SDK sets default for TFM) -->\n \u003c!-- Only set explicitly when using preview features -->\n \u003cLangVersion>preview\u003c/LangVersion>\n\u003c/PropertyGroup>\n```\n\n### Common Modification Patterns\n\n**Enabling nullable for an existing project:**\n\n```xml\n\u003c!-- Add to the main PropertyGroup -->\n\u003cNullable>enable\u003c/Nullable>\n\u003c!-- This enables nullable warnings project-wide. Existing code will produce warnings. -->\n\u003c!-- To adopt incrementally, use #nullable enable in individual files instead. -->\n```\n\n**Setting output type for a console app:**\n\n```xml\n\u003c!-- Required for executable projects; without this, dotnet run fails -->\n\u003cOutputType>Exe\u003c/OutputType>\n```\n\n**Adding TreatWarningsAsErrors (recommended for CI parity):**\n\n```xml\n\u003cPropertyGroup>\n \u003cTreatWarningsAsErrors>true\u003c/TreatWarningsAsErrors>\n \u003c!-- Enable unconditionally -- do NOT use CI-only conditions -->\n\u003c/PropertyGroup>\n```\n\n\n## Subsection 3: ItemGroup Patterns\n\nItemGroup elements contain collections: package references, project references, file inclusions, and other build items. Understanding the three main item types prevents common agent mistakes.\n\n### Annotated XML Example\n\n```xml\n\u003cItemGroup>\n \u003c!-- PackageReference: NuGet package dependency -->\n \u003c!-- Version attribute is required unless using central package management -->\n \u003cPackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"9.0.0\" />\n\n \u003c!-- PrivateAssets=\"All\" prevents the dependency from flowing to consumers -->\n \u003cPackageReference Include=\"Microsoft.SourceLink.GitHub\" Version=\"8.0.0\" PrivateAssets=\"All\" />\n\n \u003c!-- IncludeAssets controls which assets from the package are used -->\n \u003cPackageReference Include=\"Nerdbank.GitVersioning\" Version=\"3.7.115\"\n PrivateAssets=\"All\" IncludeAssets=\"runtime;build;native;analyzers\" />\n\u003c/ItemGroup>\n\n\u003cItemGroup>\n \u003c!-- ProjectReference: reference to another project in the solution -->\n \u003c!-- Use forward slashes for cross-platform compatibility -->\n \u003cProjectReference Include=\"../MyApp.Core/MyApp.Core.csproj\" />\n\n \u003c!-- Set PrivateAssets to prevent transitive exposure to consumers -->\n \u003cProjectReference Include=\"../MyApp.Internal/MyApp.Internal.csproj\"\n PrivateAssets=\"All\" />\n\u003c/ItemGroup>\n\n\u003cItemGroup>\n \u003c!-- None: files included in the project but not compiled -->\n \u003c!-- CopyToOutputDirectory controls deployment behavior -->\n \u003cNone Include=\"appsettings.json\" CopyToOutputDirectory=\"PreserveNewest\" />\n\n \u003c!-- Content: files that are part of the published output -->\n \u003cContent Include=\"wwwroot/**\" CopyToOutputDirectory=\"PreserveNewest\" />\n\n \u003c!-- EmbeddedResource: files compiled into the assembly -->\n \u003cEmbeddedResource Include=\"Resources/**/*.resx\" />\n\u003c/ItemGroup>\n```\n\n### Common Modification Patterns\n\n**Adding a NuGet package:**\n\n```bash\n# Prefer CLI to avoid formatting issues\ndotnet add src/MyApp/MyApp.csproj package Microsoft.EntityFrameworkCore --version 9.0.0\n```\n\n```xml\n\u003c!-- Or add manually -- ensure Version is specified -->\n\u003cPackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"9.0.0\" />\n```\n\n**Adding a project reference:**\n\n```bash\n# CLI ensures correct relative path\ndotnet add src/MyApp.Api/MyApp.Api.csproj reference src/MyApp.Core/MyApp.Core.csproj\n```\n\n```xml\n\u003c!-- Verify path actually resolves to an existing .csproj -->\n\u003cProjectReference Include=\"../MyApp.Core/MyApp.Core.csproj\" />\n```\n\n**Including non-compiled files in output:**\n\n```xml\n\u003c!-- Copy config files to output on build -->\n\u003cNone Update=\"config/*.json\" CopyToOutputDirectory=\"PreserveNewest\" />\n\u003c!-- Note: Update (not Include) when the file is already matched by default globs -->\n```\n\n\n## Subsection 4: Condition Expressions and Multi-Targeting\n\nMSBuild conditions enable TFM-specific properties, platform-specific package references, and build configuration logic. Understanding condition syntax prevents broken multi-targeting builds.\n\n### Annotated XML Example\n\n```xml\n\u003cProject Sdk=\"Microsoft.NET.Sdk\">\n\n \u003cPropertyGroup>\n \u003c!-- Multi-targeting: builds the project once per TFM -->\n \u003cTargetFrameworks>net8.0;net9.0\u003c/TargetFrameworks>\n \u003c!-- Note the plural 'TargetFrameworks' (not 'TargetFramework') -->\n \u003c/PropertyGroup>\n\n \u003c!-- TFM-conditional property: only applies to net9.0 builds -->\n \u003cPropertyGroup Condition=\"'$(TargetFramework)' == 'net9.0'\">\n \u003cAllowUnsafeBlocks>true\u003c/AllowUnsafeBlocks>\n \u003c/PropertyGroup>\n\n \u003c!-- TFM-conditional package reference: only include on specific TFMs -->\n \u003cItemGroup Condition=\"'$(TargetFramework)' == 'net8.0'\">\n \u003cPackageReference Include=\"Backport.System.Threading.Lock\" Version=\"2.0.5\" />\n \u003c!-- System.Threading.Lock is built-in on net9.0+; this polyfill enables it on net8.0 -->\n \u003c/ItemGroup>\n\n \u003c!-- Configuration-conditional items -->\n \u003cItemGroup Condition=\"'$(Configuration)' == 'Debug'\">\n \u003cPackageReference Include=\"Microsoft.EntityFrameworkCore.Design\" Version=\"9.0.0\" />\n \u003c/ItemGroup>\n\n \u003c!-- Platform-conditional items for MAUI/Uno -->\n \u003cItemGroup Condition=\"$(TargetFramework.StartsWith('net9.0-android'))\">\n \u003cPackageReference Include=\"Xamarin.AndroidX.Core\" Version=\"1.15.0.1\" />\n \u003c/ItemGroup>\n\n \u003c!-- Boolean conditions -->\n \u003cPropertyGroup Condition=\"'$(CI)' == 'true'\">\n \u003cContinuousIntegrationBuild>true\u003c/ContinuousIntegrationBuild>\n \u003c/PropertyGroup>\n\n\u003c/Project>\n```\n\n### Common Modification Patterns\n\n**Adding a TFM:**\n\n```xml\n\u003c!-- Change singular to plural and add new TFM -->\n\u003c!-- Before: -->\n\u003cTargetFramework>net8.0\u003c/TargetFramework>\n\u003c!-- After: -->\n\u003cTargetFrameworks>net8.0;net9.0\u003c/TargetFrameworks>\n```\n\n**Using version-agnostic TFM patterns for platform detection:**\n\n```xml\n\u003c!-- CORRECT: version-agnostic glob handles net8.0-android, net9.0-android, etc. -->\n\u003cItemGroup Condition=\"$(TargetFramework.Contains('-android'))\">\n \u003cAndroidResource Include=\"Resources/**\" />\n\u003c/ItemGroup>\n\n\u003c!-- WRONG: hardcoded version misses other TFMs -->\n\u003cItemGroup Condition=\"'$(TargetFramework)' == 'net9.0-android'\">\n```\n\n**Condition syntax reference:**\n\n| Expression | Meaning |\n|-----------|---------|\n| `'$(Prop)' == 'value'` | Exact match (case-insensitive) |\n| `'$(Prop)' != 'value'` | Not equal |\n| `$(Prop.StartsWith('prefix'))` | String starts with |\n| `$(Prop.Contains('sub'))` | String contains |\n| `'$(Prop)' == ''` | Property is empty/not set |\n| `Exists('path')` | File or directory exists |\n\n\n## Subsection 5: Directory.Build.props and Directory.Build.targets\n\nThese files centralize shared build configuration. MSBuild automatically imports `Directory.Build.props` (before the project) and `Directory.Build.targets` (after the project) from the current directory and all parent directories up to the filesystem root.\n\n### Annotated XML: Directory.Build.props\n\n```xml\n\u003c!-- Directory.Build.props: imported BEFORE the project file -->\n\u003c!-- Use for properties that projects inherit but can override -->\n\u003c!-- Place at solution root to apply to all projects -->\n\u003cProject>\n\n \u003cPropertyGroup>\n \u003c!-- Common properties for all projects in the solution -->\n \u003cTargetFramework>net9.0\u003c/TargetFramework>\n \u003cNullable>enable\u003c/Nullable>\n \u003cImplicitUsings>enable\u003c/ImplicitUsings>\n \u003cTreatWarningsAsErrors>true\u003c/TreatWarningsAsErrors>\n\n \u003c!-- Deterministic builds for CI -->\n \u003cDeterministic>true\u003c/Deterministic>\n \u003cContinuousIntegrationBuild Condition=\"'$(CI)' == 'true'\">true\u003c/ContinuousIntegrationBuild>\n \u003c/PropertyGroup>\n\n \u003cPropertyGroup>\n \u003c!-- Package metadata for all libraries -->\n \u003cAuthors>MyCompany\u003c/Authors>\n \u003cCompany>MyCompany\u003c/Company>\n \u003cPackageLicenseExpression>MIT\u003c/PackageLicenseExpression>\n \u003c/PropertyGroup>\n\n\u003c/Project>\n```\n\n### Annotated XML: Directory.Build.targets\n\n```xml\n\u003c!-- Directory.Build.targets: imported AFTER the project file -->\n\u003c!-- Use for targets, items, and properties that depend on project-level values -->\n\u003c!-- Place at solution root alongside Directory.Build.props -->\n\u003cProject>\n\n \u003c!-- Add analyzers to all projects (after project props are set) -->\n \u003cItemGroup>\n \u003cPackageReference Include=\"Microsoft.CodeAnalysis.NetAnalyzers\" Version=\"9.0.0\"\n PrivateAssets=\"All\" IncludeAssets=\"analyzers\" />\n \u003c/ItemGroup>\n\n \u003c!-- Conditional item that depends on project-set properties -->\n \u003cItemGroup Condition=\"'$(IsTestProject)' == 'true'\">\n \u003cPackageReference Include=\"coverlet.collector\" Version=\"8.0.0\"\n PrivateAssets=\"All\" />\n \u003c/ItemGroup>\n\n \u003c!-- Custom target that runs after build -->\n \u003cTarget Name=\"PrintBuildInfo\" AfterTargets=\"Build\">\n \u003cMessage Importance=\"high\" Text=\"Built $(AssemblyName) for $(TargetFramework)\" />\n \u003c/Target>\n\n\u003c/Project>\n```\n\n### Common Modification Patterns\n\n**Hierarchy and override behavior:**\n\n```\nrepo-root/\n Directory.Build.props \u003c-- applies to ALL projects\n src/\n Directory.Build.props \u003c-- applies to src/ projects only\n MyApp.Api/\n MyApp.Api.csproj \u003c-- inherits from src/ props (NOT repo-root/)\n```\n\nMSBuild imports the nearest `Directory.Build.props` found walking upward. If a nested `Directory.Build.props` exists, it shadows the parent. To chain both, the nested file must explicitly import the parent:\n\n```xml\n\u003c!-- src/Directory.Build.props -- import parent first, then override -->\n\u003cProject>\n \u003cImport Project=\"$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))\" />\n\n \u003cPropertyGroup>\n \u003c!-- Override or extend parent properties for src/ projects -->\n \u003cRootNamespace>MyApp.$(MSBuildProjectName)\u003c/RootNamespace>\n \u003c/PropertyGroup>\n\u003c/Project>\n```\n\n**When to use .props vs .targets:**\n\n| Use .props for | Use .targets for |\n|---------------|-----------------|\n| Property defaults (TFM, nullable, etc.) | Items that depend on project properties |\n| Package metadata (authors, license) | Custom build targets (AfterTargets, BeforeTargets) |\n| Properties projects can override | Analyzer packages added to all projects |\n\n\n## Subsection 6: Directory.Packages.props (Central Package Management)\n\nCentral Package Management (CPM) centralizes NuGet package versions in a single `Directory.Packages.props` file. Individual projects reference packages without specifying versions.\n\n### Annotated XML Example\n\n```xml\n\u003c!-- Directory.Packages.props: place at solution root -->\n\u003cProject>\n\n \u003cPropertyGroup>\n \u003c!-- Enable Central Package Management -->\n \u003cManagePackageVersionsCentrally>true\u003c/ManagePackageVersionsCentrally>\n \u003c/PropertyGroup>\n\n \u003cItemGroup>\n \u003c!-- PackageVersion defines the version centrally -->\n \u003c!-- Projects use PackageReference WITHOUT Version attribute -->\n \u003cPackageVersion Include=\"Microsoft.EntityFrameworkCore\" Version=\"9.0.0\" />\n \u003cPackageVersion Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"9.0.0\" />\n \u003cPackageVersion Include=\"Microsoft.Extensions.Hosting\" Version=\"9.0.0\" />\n \u003cPackageVersion Include=\"Serilog.AspNetCore\" Version=\"8.0.3\" />\n\n \u003c!-- Test packages -->\n \u003cPackageVersion Include=\"xunit.v3\" Version=\"3.2.2\" />\n \u003cPackageVersion Include=\"xunit.runner.visualstudio\" Version=\"3.1.5\" />\n \u003cPackageVersion Include=\"Microsoft.NET.Test.Sdk\" Version=\"18.0.1\" />\n \u003cPackageVersion Include=\"NSubstitute\" Version=\"5.3.0\" />\n \u003c/ItemGroup>\n\n\u003c/Project>\n```\n\n**Project file with CPM enabled:**\n\n```xml\n\u003cProject Sdk=\"Microsoft.NET.Sdk\">\n \u003cPropertyGroup>\n \u003cTargetFramework>net9.0\u003c/TargetFramework>\n \u003c/PropertyGroup>\n\n \u003cItemGroup>\n \u003c!-- No Version attribute -- version comes from Directory.Packages.props -->\n \u003cPackageReference Include=\"Microsoft.EntityFrameworkCore\" />\n \u003cPackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" />\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\n### Common Modification Patterns\n\n**Enabling CPM in an existing solution:**\n\n1. Create `Directory.Packages.props` at the solution root with `\u003cManagePackageVersionsCentrally>true\u003c/ManagePackageVersionsCentrally>`.\n2. Move all `Version` attributes from `PackageReference` items into `PackageVersion` entries in the central file.\n3. Remove `Version` from all `PackageReference` items in individual `.csproj` files.\n\n```bash\n# Find all PackageReference entries with Version attributes\ngrep -rn 'PackageReference Include=.*Version=' --include=\"*.csproj\" src/\n```\n\n**Overriding a version in a specific project** (escape hatch):\n\n```xml\n\u003c!-- In the individual .csproj -- use VersionOverride when a project needs a different version -->\n\u003cPackageReference Include=\"Microsoft.EntityFrameworkCore\" VersionOverride=\"8.0.11\" />\n```\n\n**Hierarchical resolution:** `Directory.Packages.props` resolves upward from the project directory, the same as `Directory.Build.props`. In monorepos, place the central file at the repo root. Sub-directories can have their own `Directory.Packages.props` -- the nearest one wins.\n\n**Migrating from per-project versions:**\n\n```bash\n# List all unique packages and versions across the solution\ndotnet list src/MyApp.sln package --format json\n# Use this output to build the central PackageVersion list\n```\n\n\n## Slopwatch Anti-Patterns\n\nThese patterns in project files indicate an agent is hiding problems rather than fixing them. See [skill:dotnet-testing] for the automated quality gate that detects these patterns.\n\n### NoWarn in .csproj\n\n```xml\n\u003c!-- RED FLAG: blanket warning suppression in project file -->\n\u003cPropertyGroup>\n \u003cNoWarn>CS8600;CS8602;CS8604;IL2026;IL2046;IL3050\u003c/NoWarn>\n\u003c/PropertyGroup>\n```\n\n`\u003cNoWarn>` in the project file suppresses warnings for the entire project, making issues invisible. This is worse than `#pragma` because it has no scope boundary and cannot be audited per-file.\n\n**Fix:** Remove `\u003cNoWarn>` entries and fix the underlying issues. For warnings that genuinely do not apply project-wide, configure severity in `.editorconfig` instead:\n\n```ini\n# .editorconfig -- preferred over \u003cNoWarn> for controlled suppression\n[*.cs]\ndotnet_diagnostic.CA2007.severity = none # No SynchronizationContext in ASP.NET Core\n```\n\n### Suppressed Analyzers in Directory.Build.props\n\n```xml\n\u003c!-- RED FLAG: disabling analyzers for all projects via shared props -->\n\u003cPropertyGroup>\n \u003cNoWarn>$(NoWarn);CA1062;CA1822;CA2007\u003c/NoWarn>\n \u003c!-- OR -->\n \u003cRunAnalyzers>false\u003c/RunAnalyzers>\n \u003c!-- OR -->\n \u003cEnableNETAnalyzers>false\u003c/EnableNETAnalyzers>\n\u003c/PropertyGroup>\n```\n\nDisabling analyzers in `Directory.Build.props` silences them across every project in the solution, including new projects added later. Agents sometimes do this to achieve a clean build quickly.\n\n**Fix:** Keep analyzers enabled globally. Address warnings per-project or per-file. If a specific rule category does not apply (e.g., CA2007 in ASP.NET Core apps), suppress it in `.editorconfig` at the appropriate scope with a comment explaining why.\n\n\n## Cross-References\n\n- `references/project-structure.md` -- SDK selection, project organization, solution layout\n- `references/build-analysis.md` -- interpreting build errors caused by project misconfiguration\n- [skill:dotnet-api] -- common project structure mistakes agents make (wrong SDK, broken refs)\n\n## References\n\n- [MSBuild Project SDK](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview)\n- [MSBuild Reference](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild)\n- [Central Package Management](https://learn.microsoft.com/en-us/nuget/consume-packages/Central-Package-Management)\n- [Directory.Build.props/targets](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory)\n- [MSBuild Conditions](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions)\n- [SDK-style Project Format](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":19659,"content_sha256":"090ea7789365a64f738220a7e1b34a28df0b7134bef7361f2fa51ebf6f295ce9"},{"filename":"references/documentation-strategy.md","content":"# Documentation Strategy\n\nDocumentation tooling recommendation for .NET projects: decision tree for selecting Starlight (Astro-based, modern default), Docusaurus (React-based, plugin-rich), or DocFX (community-maintained, .NET-native XML doc integration). Covers MarkdownSnippets for verified code inclusion from source files, Mermaid rendering support across all platforms, migration paths between tools, and project-context-driven recommendation based on team size, project type, and existing ecosystem.\n\n**Version assumptions:** Starlight v0.x+ (Astro 4+). Docusaurus v3.x (React 18+). DocFX v2.x (community-maintained). MarkdownSnippets as `dotnet tool` (.NET 8.0+ baseline). Mermaid v10+ (GitHub, Starlight, Docusaurus render natively).\n\n## Documentation Tooling Decision Tree\n\nChoose documentation tooling based on project context, team capabilities, and existing ecosystem investments.\n\n### Quick Decision Matrix\n\n| Factor | Starlight | Docusaurus | DocFX |\n|--------|-----------|------------|-------|\n| Best for | New projects, static docs | React teams, blog + docs | Existing .NET projects with XML docs |\n| Learning curve | Low (Markdown + MDX) | Medium (React + MDX) | Medium (.NET toolchain) |\n| Built-in search | Yes (Pagefind) | Yes (Algolia plugin) | Yes (Lunr.js) |\n| Versioned docs | Yes (manual setup) | Yes (built-in) | Yes (built-in) |\n| i18n support | Yes (built-in) | Yes (built-in) | Limited |\n| Mermaid support | Native (remark plugin) | Native (MDX plugin) | Plugin required |\n| API reference from XML | Manual integration | Manual integration | Native (`docfx metadata`) |\n| Hosting | Any static host | Any static host | Any static host |\n| Build speed | Fast (Astro) | Moderate (Webpack/Rspack) | Moderate (.NET toolchain) |\n\n### Decision Flowchart\n\n```\nStart: New documentation site for .NET project\n |\n +-- Do you have existing DocFX content?\n | |\n | +-- Yes --> Do you need XML doc API reference integration?\n | | |\n | | +-- Yes --> Stay with DocFX (lowest migration cost)\n | | |\n | | +-- No --> Migrate to Starlight (see Migration Paths below)\n | |\n | +-- No --> Is your team heavily invested in React?\n | |\n | +-- Yes --> Docusaurus (leverage React skills, plugin ecosystem)\n | |\n | +-- No --> Starlight (modern default, best DX)\n```\n\n### Project Context Factors\n\n**Library vs Application:**\n- Libraries benefit from API reference integration -- DocFX excels here with `docfx metadata` generating API docs directly from XML comments\n- Applications typically need guides, tutorials, and architectural docs -- Starlight or Docusaurus are better fits\n- Hybrid (library + app docs) -- consider Starlight with separate API reference section linking to DocFX-generated content\n\n**Team Size:**\n- Solo / small team (1-3): Starlight -- minimal configuration, fast iteration\n- Medium team (4-10): Starlight or Docusaurus -- both handle multiple contributors well with built-in versioning\n- Large team (10+): Docusaurus -- plugin ecosystem handles complex multi-author workflows, custom review integrations\n\n**Existing Ecosystem:**\n- React ecosystem: Docusaurus integrates naturally with existing React component libraries and storybook\n- .NET-only toolchain: DocFX avoids JavaScript build dependencies entirely\n- Polyglot / modern: Starlight works with any tech stack, minimal JavaScript knowledge required\n\n\n## Starlight (Astro-Based) -- Modern Default\n\nStarlight is an Astro-based documentation framework. It is the recommended default for new .NET documentation sites due to fast build times, built-in search (Pagefind), i18n, and native Mermaid support.\n\n### Initial Setup\n\n```bash\n# Create a new Starlight project\nnpm create astro@latest -- --template starlight my-docs\n\ncd my-docs\nnpm install\nnpm run dev\n```\n\n### Project Structure\n\n```\nmy-docs/\n astro.config.mjs # Starlight configuration\n src/\n content/\n docs/ # Markdown/MDX documentation pages\n index.mdx # Landing page\n getting-started/\n installation.md\n quick-start.md\n guides/\n configuration.md\n architecture.md\n reference/\n api.md\n cli.md\n assets/ # Images, diagrams\n public/ # Static assets (favicon, robots.txt)\n```\n\n### Configuration\n\n```javascript\n// astro.config.mjs\nimport { defineConfig } from 'astro/config';\nimport starlight from '@astrojs/starlight';\n\nexport default defineConfig({\n integrations: [\n starlight({\n title: 'My .NET Library',\n social: {\n github: 'https://github.com/mycompany/my-library',\n },\n sidebar: [\n {\n label: 'Getting Started',\n items: [\n { label: 'Installation', slug: 'getting-started/installation' },\n { label: 'Quick Start', slug: 'getting-started/quick-start' },\n ],\n },\n {\n label: 'Guides',\n autogenerate: { directory: 'guides' },\n },\n {\n label: 'Reference',\n autogenerate: { directory: 'reference' },\n },\n ],\n }),\n ],\n});\n```\n\n### Mermaid Support in Starlight\n\n```bash\n# Install Mermaid remark plugin\nnpm install remark-mermaidjs\n```\n\n```javascript\n// astro.config.mjs\nimport remarkMermaid from 'remark-mermaidjs';\n\nexport default defineConfig({\n markdown: {\n remarkPlugins: [remarkMermaid],\n },\n integrations: [starlight({ /* ... */ })],\n});\n```\n\nAfter configuration, use standard Mermaid fenced code blocks in any Markdown file. See `references/mermaid-diagrams.md` for .NET-specific diagram patterns.\n\n### Versioned Documentation\n\nUse the `@lorenzo_lewis/starlight-utils` plugin for version dropdown navigation -- this is the recommended approach for Starlight versioned docs.\n\nAlternatively, use directory-based versioning with explicit routing in `astro.config.mjs`:\n\n```\nsrc/content/docs/\n v1/\n getting-started.md\n api-reference.md\n v2/\n getting-started.md\n api-reference.md\n```\n\nConfigure the sidebar to point to the current version directory and add a version selector via the plugin or custom Astro component.\n\n\n## Docusaurus (React-Based)\n\nDocusaurus is a React-based documentation framework maintained by Meta. It is a strong choice for teams already invested in the React ecosystem, offering a rich plugin system, built-in blog, and versioned docs.\n\n### Initial Setup\n\n```bash\nnpx create-docusaurus@latest my-docs classic\n\ncd my-docs\nnpm install\nnpm start\n```\n\n### Project Structure\n\n```\nmy-docs/\n docusaurus.config.js # Docusaurus configuration\n docs/ # Markdown/MDX documentation\n intro.md\n getting-started/\n installation.md\n guides/\n configuration.md\n blog/ # Optional blog posts\n src/\n components/ # Custom React components\n pages/ # Custom pages\n static/ # Static assets\n sidebars.js # Sidebar configuration\n```\n\n### Configuration\n\n```javascript\n// docusaurus.config.js\n/** @type {import('@docusaurus/types').Config} */\nconst config = {\n title: 'My .NET Library',\n tagline: 'High-performance widgets for .NET',\n url: 'https://mycompany.github.io',\n baseUrl: '/my-library/',\n organizationName: 'mycompany',\n projectName: 'my-library',\n\n presets: [\n [\n 'classic',\n /** @type {import('@docusaurus/preset-classic').Options} */\n ({\n docs: {\n sidebarPath: require.resolve('./sidebars.js'),\n editUrl: 'https://github.com/mycompany/my-library/tree/main/docs/',\n },\n blog: {\n showReadingTime: true,\n },\n theme: {\n customCss: require.resolve('./src/css/custom.css'),\n },\n }),\n ],\n ],\n};\n\nmodule.exports = config;\n```\n\n### Mermaid Support in Docusaurus\n\n```bash\nnpm install @docusaurus/theme-mermaid\n```\n\n```javascript\n// docusaurus.config.js\nconst config = {\n markdown: {\n mermaid: true,\n },\n themes: ['@docusaurus/theme-mermaid'],\n // ...\n};\n```\n\n### Versioned Documentation\n\nDocusaurus has first-class versioning support:\n\n```bash\n# Snapshot current docs as a version\nnpx docusaurus docs:version 1.0\n\n# Creates versioned_docs/version-1.0/ and versions.json\n```\n\nThis creates a snapshot of the current `docs/` directory. The `docs/` directory continues to represent the \"next\" unreleased version.\n\n\n## DocFX (Community-Maintained, .NET-Native)\n\nDocFX is a .NET-native documentation generator that integrates directly with XML documentation comments. Microsoft dropped official support in November 2022, and the project is now community-maintained. It remains widely used in the .NET ecosystem, particularly for projects with heavy API reference documentation needs.\n\n### Initial Setup\n\n```bash\n# Install DocFX as a .NET tool\ndotnet tool install -g docfx\n\n# Initialize a new DocFX project\ndocfx init\n\n# Build the documentation\ndocfx build\n\n# Serve locally\ndocfx serve _site\n```\n\n### Project Structure\n\n```\ndocs/\n docfx.json # DocFX configuration\n toc.yml # Table of contents\n index.md # Landing page\n articles/ # Conceptual documentation\n getting-started.md\n configuration.md\n api/ # Auto-generated API reference\n .gitignore # Generated files excluded from source control\n images/ # Static assets\n templates/ # Custom templates (optional)\n```\n\n### Configuration\n\n```json\n{\n \"metadata\": [\n {\n \"src\": [\n {\n \"files\": [\"src/MyLibrary/MyLibrary.csproj\"],\n \"src\": \"..\"\n }\n ],\n \"dest\": \"api\",\n \"properties\": {\n \"TargetFramework\": \"net8.0\"\n }\n }\n ],\n \"build\": {\n \"content\": [\n {\n \"files\": [\"api/**.yml\", \"api/index.md\"]\n },\n {\n \"files\": [\"articles/**.md\", \"toc.yml\", \"*.md\"]\n }\n ],\n \"resource\": [\n {\n \"files\": [\"images/**\"]\n }\n ],\n \"dest\": \"_site\",\n \"globalMetadata\": {\n \"_appTitle\": \"My .NET Library\",\n \"_enableSearch\": true\n }\n }\n}\n```\n\n### XML Doc Integration\n\nDocFX's primary advantage is native API reference generation from XML documentation comments:\n\n```bash\n# Generate API metadata from project XML docs\ndocfx metadata docfx.json\n\n# This creates YAML files in the api/ directory\n# representing all public types, methods, and properties\n```\n\nThe generated API reference automatically links to conceptual articles via `uid` cross-references.\n\n### Mermaid Support in DocFX\n\nDocFX requires a template plugin for Mermaid rendering:\n\n```json\n{\n \"build\": {\n \"globalMetadata\": {\n \"_enableMermaid\": true\n },\n \"template\": [\"default\", \"modern\"],\n \"postProcessors\": [\"ExtractSearchIndex\"]\n }\n}\n```\n\nThe `modern` template includes Mermaid support since DocFX v2.75+. Earlier versions require a custom template extension. Note that `_enableMermaid` is a template-specific convention, not an officially documented DocFX property. For the `default` template, add the Mermaid JavaScript library via a custom template extension.\n\n\n## MarkdownSnippets -- Verified Code Inclusion\n\nMarkdownSnippets is a `dotnet tool` that includes verified code snippets from actual source files into Markdown documentation. This eliminates stale code examples by keeping documentation in sync with compilable source code.\n\n### Installation\n\n```bash\n# Install as a local dotnet tool\ndotnet new tool-manifest\ndotnet tool install MarkdownSnippets.Tool\n\n# Or install globally\ndotnet tool install -g MarkdownSnippets.Tool\n```\n\n### Usage\n\n**1. Mark code regions in source files with `#region` directives:**\n\n```csharp\n// src/MyLibrary/WidgetService.cs\npublic class WidgetService\n{\n #region CreateWidget\n public async Task\u003cWidget> CreateWidgetAsync(string name, CancellationToken ct = default)\n {\n ArgumentException.ThrowIfNullOrWhiteSpace(name);\n\n var widget = new Widget { Name = name, CreatedAt = DateTimeOffset.UtcNow };\n await _repository.AddAsync(widget, ct);\n return widget;\n }\n #endregion\n}\n```\n\n**2. Reference snippets in Markdown:**\n\n```markdown\n## Creating a Widget\n\nTo create a widget, use the `CreateWidgetAsync` method:\n\n\u003c!-- snippet: CreateWidget -->\n\u003c!-- endSnippet -->\n```\n\n**3. Run MarkdownSnippets to inject the code:**\n\n```bash\ndotnet tool run mdsnippets\n```\n\nThis replaces the snippet placeholder with the actual code from the source file, keeping the documentation in sync with the implementation.\n\n### Configuration\n\n```json\n// mdsnippets.json (project root)\n{\n \"Convention\": \"InPlaceOverwrite\",\n \"ReadOnly\": false,\n \"LinkFormat\": \"GitHub\",\n \"UrlPrefix\": \"https://github.com/mycompany/my-library/blob/main\",\n \"TocExcludes\": [\"**/obj/**\", \"**/bin/**\"],\n \"ExcludeDirectories\": [\"node_modules\", \".git\"]\n}\n```\n\n### Integration with Doc Platforms\n\nMarkdownSnippets works with all three documentation platforms since it operates on standard Markdown files before the platform build step:\n\n```bash\n# In your build script or CI pipeline:\ndotnet tool run mdsnippets # 1. Inject verified snippets\nnpm run build # 2. Build Starlight/Docusaurus site\n\n# For DocFX:\ndotnet tool run mdsnippets # 1. Inject verified snippets\ndocfx build # 2. Build DocFX site\n```\n\n\n## Mermaid Rendering Across Platforms\n\nAll three recommended documentation platforms support Mermaid diagrams, and GitHub renders Mermaid natively in Markdown files.\n\n| Platform | Mermaid Support | Setup Required |\n|----------|----------------|----------------|\n| GitHub | Native | None -- fenced code blocks with `mermaid` language |\n| Starlight | Plugin | `remark-mermaidjs` npm package |\n| Docusaurus | Plugin | `@docusaurus/theme-mermaid` + config flag |\n| DocFX | Template | `modern` template or custom template extension |\n\nUse standard fenced code blocks with the `mermaid` language identifier across all platforms:\n\n````markdown\n```mermaid\ngraph LR\n A[Client] --> B[API Gateway]\n B --> C[Service A]\n B --> D[Service B]\n```\n````\n\nSee `references/mermaid-diagrams.md` for .NET-specific diagram types (C4 architecture, async patterns, EF Core models, DI graphs).\n\n\n## Migration Paths\n\n### DocFX to Starlight\n\nDocFX-to-Starlight migration is the most common path for .NET projects modernizing their documentation.\n\n**Content migration:**\n1. Copy `articles/` Markdown files to `src/content/docs/`\n2. Convert `toc.yml` entries to Starlight sidebar configuration in `astro.config.mjs`\n3. Replace DocFX-specific metadata YAML headers with Starlight frontmatter\n4. Convert `xref` cross-references to standard Markdown links\n5. Replace `> [!NOTE]` / `> [!WARNING]` callout syntax with Starlight `\u003cAside>` components (or keep GitHub-compatible blockquote syntax)\n\n**API reference migration:**\n- DocFX `docfx metadata` output (YAML files) has no direct Starlight equivalent\n- Option A: Continue running `docfx metadata` and host API reference as a separate subsite\n- Option B: Use TypeDoc-style API extraction tools and convert to Markdown pages\n- Option C: Link to hosted API reference on a separate URL\n\n**Checklist:**\n\n```bash\n# Find all DocFX-specific syntax to convert\ngrep -rn \"xref:\" articles/ # Cross-references\ngrep -rn \"\\[!NOTE\\]\" articles/ # Callout syntax\ngrep -rn \"\\[!WARNING\\]\" articles/ # Callout syntax\ngrep -rn \"^\\s*uid:\" articles/ # UID metadata\n```\n\n### Docusaurus to Starlight\n\n**Content migration:**\n1. Copy `docs/` Markdown files to `src/content/docs/`\n2. Convert `sidebars.js` to Starlight sidebar configuration\n3. Replace Docusaurus-specific MDX components (`\u003cTabs>`, `\u003cTabItem>`) with Starlight equivalents\n4. Convert `docusaurus.config.js` navigation to Starlight config\n5. Migrate custom React components if used in MDX (Starlight supports Astro components and limited React via `@astrojs/react`)\n\n**Blog migration:**\n- Starlight does not have a built-in blog -- use the `starlight-blog` community plugin or maintain a separate Astro blog section\n\n\n## Agent Gotchas\n\n1. **Recommend Starlight as the modern default** -- unless the project has an existing DocFX site with heavy XML doc integration or the team is deeply invested in React (Docusaurus). Do not recommend DocFX for new projects without XML doc API reference needs.\n\n2. **DocFX is community-maintained since November 2022** -- Microsoft transferred the repository to the community. It is still actively maintained and widely used, but new projects should evaluate Starlight or Docusaurus first.\n\n3. **MarkdownSnippets runs BEFORE the doc platform build** -- it is a pre-processing step that modifies Markdown files in place. Always run `dotnet tool run mdsnippets` before `npm run build` (Starlight/Docusaurus) or `docfx build` (DocFX).\n\n4. **Do not generate CI deployment YAML** -- doc site deployment workflows belong to [skill:dotnet-devops]. This skill covers tooling selection and local authoring setup only.\n\n5. **Do not generate API reference configuration** -- DocFX API reference setup, OpenAPI-as-documentation patterns, and doc-code sync belong to [skill:dotnet-api]. This skill helps choose the platform, not configure API reference generation.\n\n6. **Mermaid fenced code blocks work identically across GitHub, Starlight, and Docusaurus** -- use the same `mermaid` language identifier everywhere. Only DocFX requires additional template configuration.\n\n7. **MarkdownSnippets `#region` names must be unique across the entire solution** -- duplicate region names cause ambiguous snippet resolution. Use descriptive names like `CreateWidgetAsync` not generic names like `Example1`.\n\n8. **Starlight versioning is directory-based, not command-based** -- unlike Docusaurus (`npx docusaurus docs:version`), Starlight uses directory structure or community plugins for versioning. Do not suggest Docusaurus versioning commands for Starlight projects.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":17981,"content_sha256":"402b37716e8df903f6c4a62c4433f10f3e3f8e8b0c98133d85e85640a885bfc1"},{"filename":"references/dotnet-sdk-install.md","content":"# .NET SDK Installation and Workload Management\n\nInstall the .NET SDK when it is missing, manage workloads, and configure environment variables so agents and CI systems can build and run .NET applications. The install script is fast (under 60 seconds on a decent connection) and requires no admin/root privileges.\n\n**Do not give up if the .NET SDK is not installed.** Install it using the script below and continue working.\n\n## Detect Whether .NET Is Available\n\nCheck for the SDK before assuming it is missing:\n\n```bash\n# Quick check -- exits 0 if dotnet is on PATH\ncommand -v dotnet >/dev/null 2>&1 && dotnet --version\n\n# Check DOTNET_ROOT if 'dotnet' isn't on PATH\n[ -x \"${DOTNET_ROOT:-$HOME/.dotnet}/dotnet\" ] && \"${DOTNET_ROOT:-$HOME/.dotnet}/dotnet\" --version\n\n# List installed SDKs and runtimes\ndotnet --list-sdks\ndotnet --list-runtimes\n```\n\nIf `dotnet` is not found, install it. This is a normal situation -- the install is quick and non-destructive.\n\n## Install .NET SDK (Linux / macOS)\n\nThe official `dotnet-install.sh` script installs the SDK to `~/.dotnet` by default. No `sudo` required.\n\n### One-Liner Install (Latest LTS)\n\n```bash\ncurl -sSL https://dot.net/v1/dotnet-install.sh | bash\n```\n\n### Install a Specific Channel\n\n```bash\n# Download the script first (recommended for agents)\ncurl -sSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh\nchmod +x dotnet-install.sh\n\n# Install .NET 10 SDK (LTS)\n./dotnet-install.sh --channel 10.0\n\n# Install .NET 9 SDK (STS)\n./dotnet-install.sh --channel 9.0\n\n# Install .NET 8 SDK (LTS)\n./dotnet-install.sh --channel 8.0\n\n# Install a specific version\n./dotnet-install.sh --version 10.0.100\n```\n\n### Export Environment Variables\n\nAfter installing, make `dotnet` available in the current session and persist it:\n\n```bash\nexport DOTNET_ROOT=\"$HOME/.dotnet\"\nexport PATH=\"$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools\"\n\n# Persist across sessions (add to shell profile)\necho 'export DOTNET_ROOT=\"$HOME/.dotnet\"' >> ~/.bashrc\necho 'export PATH=\"$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools\"' >> ~/.bashrc\n\n# For zsh (macOS default)\necho 'export DOTNET_ROOT=\"$HOME/.dotnet\"' >> ~/.zshrc\necho 'export PATH=\"$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools\"' >> ~/.zshrc\n```\n\n### Full Agent-Ready Install Script\n\nCopy-paste this block to install .NET and make it immediately available:\n\n```bash\ncurl -sSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh\nchmod +x /tmp/dotnet-install.sh\n/tmp/dotnet-install.sh --channel 10.0\nexport DOTNET_ROOT=\"$HOME/.dotnet\"\nexport PATH=\"$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools\"\ndotnet --version\n```\n\n## Install .NET SDK (Windows)\n\nUse the PowerShell install script:\n\n```powershell\n# Download and run\nInvoke-WebRequest 'https://dot.net/v1/dotnet-install.ps1' -OutFile 'dotnet-install.ps1'\n\n# Install latest LTS\n./dotnet-install.ps1 -Channel LTS\n\n# Install .NET 10\n./dotnet-install.ps1 -Channel 10.0\n\n# Install to custom directory\n./dotnet-install.ps1 -Channel 10.0 -InstallDir \"$env:USERPROFILE\\.dotnet\"\n```\n\nSet environment variables:\n\n```powershell\n$env:DOTNET_ROOT = \"$env:USERPROFILE\\.dotnet\"\n$env:PATH += \";$env:DOTNET_ROOT;$env:DOTNET_ROOT\\tools\"\n\n# Persist\n[Environment]::SetEnvironmentVariable(\"DOTNET_ROOT\", \"$env:USERPROFILE\\.dotnet\", \"User\")\n[Environment]::SetEnvironmentVariable(\"PATH\", \"$env:PATH;$env:DOTNET_ROOT;$env:DOTNET_ROOT\\tools\", \"User\")\n```\n\n## Install Script Parameter Reference\n\n| Parameter (bash / PowerShell) | Description | Default |\n|-------------------------------|-------------|---------|\n| `--channel` / `-Channel` | Release channel: `LTS`, `STS`, `10.0`, `9.0`, `8.0` | `LTS` |\n| `--version` / `-Version` | Specific SDK version (e.g., `10.0.100`) or `latest` | `latest` |\n| `--install-dir` / `-InstallDir` | Installation directory | `~/.dotnet` (Linux/macOS), `%LocalAppData%\\Microsoft\\dotnet` (Windows) |\n| `--runtime` / `-Runtime` | Install runtime only: `dotnet`, `aspnetcore`, `windowsdesktop` | (installs SDK) |\n| `--architecture` / `-Architecture` | Target arch: `x64`, `arm64`, `x86` | Auto-detected |\n| `--quality` / `-Quality` | Build quality: `GA`, `preview`, `daily` | (channel default) |\n| `--no-path` / `-NoPath` | Don't modify PATH for current session | (PATH is modified) |\n| `--dry-run` / `-DryRun` | Show what would be installed without installing | (off) |\n| `--skip-non-versioned-files` | Keep existing `dotnet` binary when installing older version alongside newer | (off) |\n| `--jsonfile` / `-JSonFile` | Use `global.json` to determine SDK version | (none) |\n| `--verbose` / `-Verbose` | Show detailed output | (off) |\n\n## Install Multiple SDK Versions Side-by-Side\n\n```bash\n# Install .NET 10 first (newer)\n./dotnet-install.sh --channel 10.0\n\n# Install .NET 8 alongside without overwriting the dotnet binary\n./dotnet-install.sh --channel 8.0 --skip-non-versioned-files\n\n# Verify both are available\ndotnet --list-sdks\n```\n\nUse `global.json` to pin a project to a specific SDK version:\n\n```bash\n# Pin from global.json in the repo\n./dotnet-install.sh --jsonfile ./global.json\n```\n\n## Workload Management\n\nWorkloads extend the SDK with additional platform support (MAUI, Aspire, WASM, etc.).\n\n```bash\n# List available workloads\ndotnet workload search\n\n# List installed workloads\ndotnet workload list\n\n# Install a workload\ndotnet workload install maui\ndotnet workload install aspire\ndotnet workload install wasm-tools\n\n# Update all installed workloads\ndotnet workload update\n\n# Restore workloads declared in a project\ndotnet workload restore\n\n# Uninstall a workload\ndotnet workload uninstall maui\n```\n\n### Common Workloads\n\n| Workload | Purpose |\n|----------|---------|\n| `aspire` | .NET Aspire cloud-native development |\n| `maui` | .NET MAUI cross-platform mobile/desktop |\n| `wasm-tools` | WebAssembly ahead-of-time compilation |\n| `wasm-experimental` | Experimental WASM features |\n| `ios` | iOS development (macOS only) |\n| `android` | Android development |\n| `maccatalyst` | Mac Catalyst development |\n\n## .NET Global Tools\n\n```bash\n# Install a global tool\ndotnet tool install -g dotnet-ef\ndotnet tool install -g dotnet-outdated-tool\ndotnet tool install -g dotnet-dump\n\n# Update a global tool\ndotnet tool update -g dotnet-ef\n\n# List installed global tools\ndotnet tool list -g\n\n# Restore local tools from manifest\ndotnet tool restore\n```\n\n## Verify Installation\n\n```bash\ndotnet --info # Full SDK, runtime, and environment info\ndotnet --version # SDK version only\ndotnet --list-sdks # All installed SDKs\ndotnet --list-runtimes # All installed runtimes\ndotnet new list # Available project templates\n```\n\n## Agent Gotchas\n\n1. **Do not assume .NET is unavailable** -- check `DOTNET_ROOT` and `~/.dotnet/dotnet` before reporting that .NET cannot be used. The install script takes under a minute.\n2. **Always export `DOTNET_ROOT` and update `PATH`** after installing -- without these, `dotnet` won't be found in the current shell session.\n3. **Use `--channel` not `--version` for most installs** -- `--channel 10.0` gets the latest patch, while `--version` pins to an exact build.\n4. **Use `--skip-non-versioned-files` when installing older SDKs alongside newer ones** -- otherwise the older `dotnet` binary overwrites the newer one.\n5. **Workloads may need `sudo` on system-wide installs** -- but not for `~/.dotnet` installs. If workload install fails with permission errors, check whether `dotnet` is a system package.\n6. **The install script does not install native dependencies** -- on Linux, you may still need `libicu`, `libssl`, or `libgcc_s`. Check the distro-specific docs if `dotnet` fails at runtime.\n7. **`global.json` controls which SDK version is used** -- if a repo has `global.json` but that SDK version isn't installed, `dotnet` commands fail. Install it with `./dotnet-install.sh --jsonfile ./global.json`.\n\n## References\n\n- [dotnet-install scripts reference](https://learn.microsoft.com/dotnet/core/tools/dotnet-install-script)\n- [Install .NET on Linux](https://learn.microsoft.com/dotnet/core/install/linux-scripted-manual)\n- [Install .NET on macOS](https://learn.microsoft.com/dotnet/core/install/macos)\n- [.NET workload management](https://learn.microsoft.com/dotnet/core/tools/dotnet-workload)\n- [.NET global tools](https://learn.microsoft.com/dotnet/core/tools/global-tools)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":8241,"content_sha256":"4e5c9a53412bf47d4dbd3ef3aee56ed81c71ff4a4244ce3ec2ad9e0013ad2e65"},{"filename":"references/gc-memory.md","content":"# GC and Memory\n\nGarbage collection and memory management for .NET applications. Covers GC modes (workstation vs server, concurrent vs non-concurrent), Large Object Heap (LOH) and Pinned Object Heap (POH), generational tuning (Gen0/1/2), memory pressure notifications, deep Span\u003cT>/Memory\u003cT> ownership patterns beyond basics, buffer pooling with ArrayPool\u003cT> and MemoryPool\u003cT>, weak references, finalizers vs IDisposable, and memory profiling with dotMemory and PerfView.\n\n## GC Modes and Configuration\n\n### Workstation vs Server GC\n\n| Aspect | Workstation | Server |\n|--------|-------------|--------|\n| **GC threads** | Single thread | One thread per logical core |\n| **Heap segments** | Single heap | One heap per core |\n| **Pause latency** | Lower | Higher (more memory scanned) |\n| **Throughput** | Lower | Higher |\n| **Default for** | Console apps, desktop | ASP.NET Core web apps |\n\n```xml\n\u003c!-- In the .csproj file -->\n\u003cPropertyGroup>\n \u003cServerGarbageCollection>true\u003c/ServerGarbageCollection>\n\u003c/PropertyGroup>\n```\n\n```json\n// Or in runtimeconfig.json\n{\n \"runtimeOptions\": {\n \"configProperties\": {\n \"System.GC.Server\": true\n }\n }\n}\n```\n\n### Concurrent vs Non-Concurrent GC\n\n| Mode | Behavior | Use when |\n|------|----------|----------|\n| **Concurrent** (default) | Gen2 collection runs alongside application threads | Latency-sensitive (web APIs, UI) |\n| **Non-concurrent** | Application threads pause during Gen2 collection | Maximum throughput, batch processing |\n\n```json\n{\n \"runtimeOptions\": {\n \"configProperties\": {\n \"System.GC.Concurrent\": true\n }\n }\n}\n```\n\n### DATAS (Dynamic Adaptation to Application Sizes) -- .NET 8+\n\nDATAS dynamically adjusts GC heap size based on application memory usage patterns. It is enabled by default in .NET 8+ Server GC mode. DATAS reduces memory footprint for applications with variable load by shrinking the heap during low-activity periods.\n\n```json\n{\n \"runtimeOptions\": {\n \"configProperties\": {\n \"System.GC.DynamicAdaptationMode\": 1\n }\n }\n}\n```\n\nSet to `0` to disable DATAS if you observe excessive GC frequency in steady-state workloads.\n\n### GC Regions -- .NET 7+\n\nRegions replace the older segment-based heap management. Each region is a small, fixed-size block of memory that the GC can allocate and free independently. Regions are enabled by default in .NET 7+ and improve:\n\n- Memory return to the OS after usage spikes\n- Heap compaction efficiency\n- Server GC scalability on high-core-count machines\n\nNo configuration is needed -- regions are the default. To revert to segments (rarely needed):\n\n```json\n{\n \"runtimeOptions\": {\n \"configProperties\": {\n \"System.GC.Regions\": false\n }\n }\n}\n```\n\n\n## Generational GC (Gen0/1/2)\n\n### How Generations Work\n\n| Generation | Contains | Collection frequency | Collection cost |\n|-----------|----------|---------------------|----------------|\n| **Gen0** | Newly allocated objects | Very frequent (milliseconds) | Very cheap (small heap) |\n| **Gen1** | Objects surviving Gen0 | Frequent | Cheap |\n| **Gen2** | Long-lived objects | Infrequent | Expensive (full heap scan) |\n\nObjects promote from Gen0 to Gen1 to Gen2 as they survive collections. The GC budget for Gen0 is tuned dynamically -- when Gen0 fills, a Gen0 collection triggers.\n\n### Tuning Principles\n\n1. **Minimize Gen0 allocation rate** -- reduce temporary object creation on hot paths. Every allocation contributes to Gen0 pressure.\n2. **Avoid mid-life crisis** -- objects that live just long enough to promote to Gen1/Gen2 but then become garbage are the most expensive. They survive cheap Gen0 collections and require expensive Gen2 collections to reclaim.\n3. **Reduce Gen2 collection frequency** -- Gen2 collections cause the longest pauses. Use object pooling, Span\u003cT>, and value types to keep long-lived heap allocations low.\n\n### Monitoring Generations\n\n```bash\n# Real-time GC metrics\ndotnet-counters monitor --process-id \u003cPID> \\\n --counters System.Runtime[gen-0-gc-count,gen-1-gc-count,gen-2-gc-count,gc-heap-size]\n```\n\n```csharp\n// Programmatic GC observation\nvar gen0 = GC.CollectionCount(0);\nvar gen1 = GC.CollectionCount(1);\nvar gen2 = GC.CollectionCount(2);\nvar totalMemory = GC.GetTotalMemory(forceFullCollection: false);\nvar memoryInfo = GC.GetGCMemoryInfo();\n\nlogger.LogInformation(\n \"GC: Gen0={Gen0} Gen1={Gen1} Gen2={Gen2} Heap={HeapMB:F1}MB\",\n gen0, gen1, gen2, totalMemory / (1024.0 * 1024));\n```\n\n\n## Large Object Heap (LOH) and Pinned Object Heap (POH)\n\n### LOH\n\nObjects >= 85,000 bytes are allocated on the LOH. LOH collections only happen during Gen2 collections, and by default the LOH is not compacted (causing fragmentation).\n\n```csharp\n// Force LOH compaction (use sparingly -- expensive)\nGCSettings.LargeObjectHeapCompactionMode =\n GCLargeObjectHeapCompactionMode.CompactOnce;\nGC.Collect();\n```\n\n### LOH Fragmentation Prevention\n\n| Strategy | Implementation |\n|----------|---------------|\n| **ArrayPool\u003cT>** for large arrays | `ArrayPool\u003cbyte>.Shared.Rent(100_000)` |\n| **MemoryPool\u003cT>** for IMemoryOwner pattern | `MemoryPool\u003cbyte>.Shared.Rent(100_000)` |\n| **Pre-allocate and reuse** | Create large buffers once at startup |\n| **Avoid frequent large string concat** | Use `StringBuilder` or `string.Create` |\n\n### POH (Pinned Object Heap) -- .NET 5+\n\nThe POH is a dedicated heap for objects that must remain at a fixed memory address (pinned). Before .NET 5, pinning objects on the regular heap prevented compaction. The POH isolates pinned objects so they do not block compaction of Gen0/1/2 heaps.\n\n```csharp\n// Allocate on POH -- useful for I/O buffers passed to native code\nbyte[] buffer = GC.AllocateArray\u003cbyte>(4096, pinned: true);\n\n// The buffer's address will not change, safe for native interop\n// and overlapped I/O without explicit GCHandle pinning\n```\n\nUse POH for:\n- I/O buffers passed to native/unmanaged code\n- Memory-mapped file backing arrays\n- Buffers used with `Socket.ReceiveAsync` (overlapped I/O)\n\n\n## Span\u003cT>/Memory\u003cT> Deep Ownership Patterns\n\nSee `references/performance-patterns.md` for Span\u003cT>/Memory\u003cT> introduction and basic slicing. This section covers ownership semantics and lifetime management for shared buffers.\n\n### IMemoryOwner\u003cT> for Pooled Buffers\n\n```csharp\n// Rent from MemoryPool and manage lifetime with IDisposable\nusing IMemoryOwner\u003cbyte> owner = MemoryPool\u003cbyte>.Shared.Rent(4096);\nMemory\u003cbyte> buffer = owner.Memory[..4096]; // Slice to exact size needed\n\n// Pass the Memory\u003cT> to async I/O\nint bytesRead = await stream.ReadAsync(buffer, cancellationToken);\nMemory\u003cbyte> data = buffer[..bytesRead];\n\n// Process the data\nawait ProcessDataAsync(data, cancellationToken);\n// owner.Dispose() returns the buffer to the pool\n```\n\n### Ownership Transfer Pattern\n\nWhen transferring buffer ownership between components, use `IMemoryOwner\u003cT>` to make lifetime responsibility explicit:\n\n```csharp\npublic sealed class MessageParser\n{\n // Caller transfers ownership -- this method is responsible for disposal\n public async Task ProcessAsync(\n IMemoryOwner\u003cbyte> messageOwner,\n CancellationToken ct)\n {\n using (messageOwner)\n {\n Memory\u003cbyte> data = messageOwner.Memory;\n // Parse and process...\n await HandleMessageAsync(data, ct);\n }\n // Buffer returned to pool on dispose\n }\n}\n```\n\n### Span\u003cT> Stack Discipline\n\n```csharp\n// Span\u003cT> enforces stack-only usage (ref struct)\n// These are compile-time errors:\n// Span\u003cbyte> field; // Cannot store in class/struct field\n// async Task Foo(Span\u003cbyte> s); // Cannot use in async method\n// var list = new List\u003cSpan\u003cbyte>>(); // Cannot use as generic type argument\n\n// When you need heap storage or async, use Memory\u003cT> instead\npublic async Task ProcessAsync(Memory\u003cbyte> buffer, CancellationToken ct)\n{\n // Can use Memory\u003cT> in async methods\n int bytesRead = await stream.ReadAsync(buffer, ct);\n\n // Convert to Span\u003cT> for synchronous processing within a method\n Span\u003cbyte> span = buffer.Span;\n ParseHeader(span[..bytesRead]);\n}\n```\n\n\n## ArrayPool\u003cT> and MemoryPool\u003cT>\n\n### ArrayPool\u003cT>\n\n`ArrayPool\u003cT>` reduces GC pressure by reusing array allocations. Always return rented arrays, and never assume the returned array is exactly the requested size.\n\n```csharp\n// Rent and return pattern\nbyte[] buffer = ArrayPool\u003cbyte>.Shared.Rent(minimumLength: 4096);\ntry\n{\n // IMPORTANT: Rented array may be larger than requested\n int bytesRead = await stream.ReadAsync(\n buffer.AsMemory(0, 4096), cancellationToken);\n ProcessData(buffer.AsSpan(0, bytesRead));\n}\nfinally\n{\n // clearArray: true when buffer contained sensitive data\n ArrayPool\u003cbyte>.Shared.Return(buffer, clearArray: false);\n}\n```\n\n### Custom Pool Sizing\n\n```csharp\n// Create a custom pool for specific allocation patterns\nvar pool = ArrayPool\u003cbyte>.Create(\n maxArrayLength: 1_048_576, // 1 MB max array\n maxArraysPerBucket: 50); // Keep up to 50 arrays per size bucket\n\n// Use for workloads with predictable buffer sizes\nbyte[] buffer = pool.Rent(65_536);\ntry\n{\n // Process...\n}\nfinally\n{\n pool.Return(buffer);\n}\n```\n\n### MemoryPool\u003cT>\n\n`MemoryPool\u003cT>` wraps `ArrayPool\u003cT>` and returns `IMemoryOwner\u003cT>` for RAII-style lifetime management:\n\n```csharp\n// MemoryPool returns IMemoryOwner\u003cT> -- dispose to return\nusing IMemoryOwner\u003cbyte> owner = MemoryPool\u003cbyte>.Shared.Rent(8192);\nMemory\u003cbyte> buffer = owner.Memory;\n\n// Slice to exact size (owner.Memory may be larger)\nint bytesRead = await stream.ReadAsync(buffer[..8192], ct);\nawait ProcessAsync(buffer[..bytesRead], ct);\n// Dispose returns the underlying array to the pool\n```\n\n### Pool Usage Guidelines\n\n| Guideline | Rationale |\n|-----------|-----------|\n| Always return rented buffers in `finally` or `using` | Leaked buffers defeat the purpose of pooling |\n| Slice to exact size before processing | Rented arrays may be larger than requested |\n| Use `clearArray: true` for sensitive data | Pool reuse could expose secrets to other consumers |\n| Do not cache rented arrays in long-lived fields | Holds pool buffers indefinitely, reducing availability |\n| Prefer `MemoryPool\u003cT>` over raw `ArrayPool\u003cT>` | Disposal-based lifetime is harder to misuse |\n\n\n## Weak References and Caching\n\n### WeakReference\u003cT>\n\nWeak references allow the GC to collect the target object when no strong references remain. Use for caches where reclamation under memory pressure is acceptable.\n\n```csharp\npublic sealed class ImageCache\n{\n private readonly ConcurrentDictionary\u003cstring, WeakReference\u003cbyte[]>> _cache = new();\n\n public byte[]? TryGet(string key)\n {\n if (_cache.TryGetValue(key, out var weakRef)\n && weakRef.TryGetTarget(out var data))\n {\n return data;\n }\n return null;\n }\n\n public void Set(string key, byte[] data)\n {\n _cache[key] = new WeakReference\u003cbyte[]>(data);\n }\n\n // Periodically clean up dead references\n public void Purge()\n {\n foreach (var key in _cache.Keys)\n {\n if (_cache.TryGetValue(key, out var weakRef)\n && !weakRef.TryGetTarget(out _))\n {\n _cache.TryRemove(key, out _);\n }\n }\n }\n}\n```\n\n### When to Use Weak References\n\n- Large object caches where memory pressure should trigger eviction\n- Caches for expensive-to-compute but recreatable data (image thumbnails, rendered templates)\n- Do NOT use for small objects -- the `WeakReference\u003cT>` overhead outweighs the benefit\n\nFor most caching scenarios, prefer `MemoryCache` with size limits and expiration policies. Weak references are a last resort when you need GC-driven eviction.\n\n\n## Finalizers vs IDisposable\n\n### IDisposable (Preferred)\n\nImplement `IDisposable` to release unmanaged resources deterministically:\n\n```csharp\npublic sealed class NativeBufferWrapper : IDisposable\n{\n private IntPtr _handle;\n private bool _disposed;\n\n public NativeBufferWrapper(int size)\n {\n _handle = Marshal.AllocHGlobal(size);\n }\n\n public void Dispose()\n {\n if (_disposed) return;\n _disposed = true;\n\n Marshal.FreeHGlobal(_handle);\n _handle = IntPtr.Zero;\n // No GC.SuppressFinalize needed -- no finalizer\n }\n}\n```\n\n### Finalizer (Safety Net Only)\n\nFinalizers run on the GC finalizer thread when an object is collected. They are a safety net for unmanaged resources that were not disposed explicitly.\n\n```csharp\npublic class UnmanagedResourceHolder : IDisposable\n{\n private IntPtr _handle;\n private bool _disposed;\n\n public UnmanagedResourceHolder(int size)\n {\n _handle = Marshal.AllocHGlobal(size);\n }\n\n ~UnmanagedResourceHolder()\n {\n Dispose(disposing: false);\n }\n\n public void Dispose()\n {\n Dispose(disposing: true);\n GC.SuppressFinalize(this);\n }\n\n protected virtual void Dispose(bool disposing)\n {\n if (_disposed) return;\n _disposed = true;\n\n if (disposing)\n {\n // Free managed resources\n }\n\n // Free unmanaged resources\n if (_handle != IntPtr.Zero)\n {\n Marshal.FreeHGlobal(_handle);\n _handle = IntPtr.Zero;\n }\n }\n}\n```\n\n### Finalizer Costs\n\n| Cost | Impact |\n|------|--------|\n| Objects with finalizers survive at least one extra GC | Promotes to Gen1/Gen2, increasing memory pressure |\n| Finalizer thread is single-threaded | Slow finalizers block all other finalization |\n| Execution order is non-deterministic | Cannot depend on other finalizable objects |\n| Not guaranteed to run on process exit | Critical cleanup may not execute |\n\n**Rule:** Use `sealed` classes with `IDisposable` (no finalizer) unless you own unmanaged handles. Only add a finalizer as a safety net for unmanaged resources.\n\n\n## Memory Pressure Notifications\n\n### GC.AddMemoryPressure / RemoveMemoryPressure\n\nInform the GC about unmanaged memory allocations so it accounts for them in collection decisions:\n\n```csharp\npublic sealed class NativeImageBuffer : IDisposable\n{\n private readonly IntPtr _buffer;\n private readonly long _size;\n private bool _disposed;\n\n public NativeImageBuffer(long sizeBytes)\n {\n _size = sizeBytes;\n _buffer = Marshal.AllocHGlobal((IntPtr)sizeBytes);\n GC.AddMemoryPressure(sizeBytes);\n }\n\n public void Dispose()\n {\n if (_disposed) return;\n _disposed = true;\n\n Marshal.FreeHGlobal(_buffer);\n GC.RemoveMemoryPressure(_size);\n }\n}\n```\n\n### GC.GetGCMemoryInfo for Adaptive Behavior\n\n```csharp\n// React to memory pressure in application logic\nvar memoryInfo = GC.GetGCMemoryInfo();\ndouble loadPercent = (double)memoryInfo.MemoryLoadBytes\n / memoryInfo.TotalAvailableMemoryBytes * 100;\n\nif (loadPercent > 85)\n{\n logger.LogWarning(\"High memory pressure: {Load:F1}%\", loadPercent);\n // Shed load: reduce cache sizes, reject non-critical requests\n}\n```\n\n\n## Memory Profiling\n\n### dotMemory (JetBrains)\n\ndotMemory provides heap snapshots and allocation tracking with a visual UI. Use it for investigating memory leaks and high-allocation hot paths.\n\n**Workflow:**\n1. Attach dotMemory to the running process (or launch with profiling enabled)\n2. Capture a baseline snapshot after application warm-up\n3. Execute the scenario under investigation\n4. Capture a second snapshot\n5. Compare snapshots to identify retained objects and growth\n\n**Key views:**\n- **Sunburst** -- shows allocation tree by type hierarchy\n- **Dominator tree** -- shows which objects prevent GC of retained memory\n- **Survived objects** -- objects allocated between snapshots that survived GC\n\n### PerfView\n\nPerfView is a free Microsoft tool for detailed GC and allocation analysis. It uses ETW (Event Tracing for Windows) events for low-overhead profiling.\n\n```bash\n# Collect GC and allocation events for 30 seconds\nPerfView.exe /GCCollectOnly /MaxCollectSec:30 collect\n\n# Collect allocation stacks (higher overhead)\nPerfView.exe /ClrEvents:GC+Stack /MaxCollectSec:30 collect\n```\n\n**Key PerfView views:**\n- **GCStats** -- GC pause times, generation counts, promotion rates, fragmentation\n- **GC Heap Alloc Stacks** -- call stacks responsible for allocations\n- **Any Stacks** -- CPU sampling for identifying hot methods\n\n### Profiling Workflow\n\n1. **Identify the symptom** -- high memory usage, growing Gen2, frequent Gen2 collections, LOH fragmentation\n2. **Monitor with dotnet-counters** (see `references/profiling.md`) to confirm GC metrics match the symptom\n3. **Profile with dotMemory or PerfView** to identify the objects and allocation sites\n4. **Apply fixes** -- pool buffers, use Span\u003cT>, reduce allocations, fix leaks\n5. **Validate with BenchmarkDotNet** (see [skill:dotnet-testing]) `[MemoryDiagnoser]` to confirm improvement\n6. **Monitor in production** via OpenTelemetry runtime metrics (see [skill:dotnet-devops])\n\n\n## Agent Gotchas\n\n1. **Do not default to workstation GC for ASP.NET Core applications** -- server GC is the default and correct choice for web workloads. Workstation GC has lower throughput on multi-core servers. Only override for specific latency-sensitive scenarios.\n2. **Do not forget to return ArrayPool buffers** -- leaked pool buffers are worse than regular allocations because they hold pool capacity indefinitely. Always use `try/finally` or `IMemoryOwner\u003cT>` with `using`.\n3. **Do not assume rented arrays are the requested size** -- `ArrayPool\u003cT>.Rent()` may return an array larger than requested. Always slice to the exact size needed before processing.\n4. **Do not add finalizers to classes that only use managed resources** -- finalizers promote objects to Gen1/Gen2 and add overhead to GC. Use `sealed class` with `IDisposable` (no finalizer) for managed-only cleanup.\n5. **Do not call GC.Collect() in production code** -- forcing full collections causes long pauses and disrupts the GC's dynamic tuning. Use `GC.AddMemoryPressure()` to hint at unmanaged memory instead.\n6. **Do not ignore LOH fragmentation** -- large arrays (>= 85,000 bytes) allocated and freed repeatedly fragment the LOH. Use `ArrayPool\u003cT>` to rent and return large buffers instead of allocating new arrays.\n7. **Do not cache IMemoryOwner\u003cT> in long-lived fields without disposal tracking** -- the underlying pooled buffer is held indefinitely, preventing pool reuse. Transfer ownership explicitly or limit cache lifetimes.\n\n\n## References\n\n- [Fundamentals of garbage collection](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals)\n- [Workstation and server GC](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/workstation-server-gc)\n- [Large Object Heap](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap)\n- [Pinned Object Heap](https://devblogs.microsoft.com/dotnet/internals-of-the-poh/)\n- [Memory\u003cT> and Span\u003cT> usage guidelines](https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/memory-t-usage-guidelines)\n- [ArrayPool\u003cT> class](https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraypool-1)\n- [GC.GetGCMemoryInfo](https://learn.microsoft.com/en-us/dotnet/api/system.gc.getgcmemoryinfo)\n- [PerfView GC analysis tutorial](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/debug-highcpu?tabs=windows#analyze-with-perfview)\n- [Stephen Toub -- Performance Improvements in .NET series](https://devblogs.microsoft.com/dotnet/author/toub/) (published annually)\n- [IDisposable pattern](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":19587,"content_sha256":"ebefa08954c5bdbc7c6c1d91ae839cdadcdcfc9bd6a2643d2667ca3e6cfcb650"},{"filename":"references/ilspy-decompile.md","content":"# ILSpy Decompilation\n\nDecompile .NET assemblies to understand API internals, inspect NuGet package source, view framework implementation details, or analyze compiled binaries. Uses `ilspycmd` (the CLI for ILSpy).\n\n## Quick Start\n\n```bash\n# .NET 10+: run via dnx without installing\ndnx ilspycmd MyLibrary.dll\n\n# Or install as a global tool\ndotnet tool install -g ilspycmd\nilspycmd MyLibrary.dll\n```\n\n## Common Assembly Locations\n\n### NuGet Packages\n\n```bash\n# Global packages cache\n~/.nuget/packages/\u003cpackage-name>/\u003cversion>/lib/\u003ctfm>/\n\n# Examples\n~/.nuget/packages/newtonsoft.json/13.0.3/lib/netstandard2.0/Newtonsoft.Json.dll\n~/.nuget/packages/microsoft.extensions.dependencyinjection/9.0.0/lib/net9.0/Microsoft.Extensions.DependencyInjection.dll\n~/.nuget/packages/polly.core/8.5.0/lib/net8.0/Polly.Core.dll\n```\n\n### .NET Runtime Libraries\n\n```bash\n# Find install location\ndotnet --list-runtimes\n\n# Runtime assemblies\n# Linux/macOS\n/usr/share/dotnet/shared/Microsoft.NETCore.App/\u003cversion>/\n/usr/share/dotnet/shared/Microsoft.AspNetCore.App/\u003cversion>/\n\n# macOS (Homebrew)\n/usr/local/share/dotnet/shared/Microsoft.NETCore.App/\u003cversion>/\n\n# Windows\nC:/Program Files/dotnet/shared/Microsoft.NETCore.App/\u003cversion>/\nC:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/\u003cversion>/\n\n# Home-dir install\n~/.dotnet/shared/Microsoft.NETCore.App/\u003cversion>/\n```\n\n### Project Build Output\n\n```bash\n./bin/Debug/net10.0/\u003cAssemblyName>.dll\n./bin/Release/net10.0/publish/\u003cAssemblyName>.dll\n```\n\n## Commands\n\n### Basic Decompilation\n\n```bash\n# Decompile to stdout\nilspycmd MyLibrary.dll\n\n# Decompile to output directory\nilspycmd -o ./decompiled MyLibrary.dll\n\n# Decompile as compilable project\nilspycmd -p -o ./project MyLibrary.dll\n\n# Decompile with nested namespace folders\nilspycmd -p -o ./project --nested-directories MyLibrary.dll\n```\n\n### Targeted Decompilation\n\n```bash\n# Decompile a specific type\nilspycmd -t Namespace.ClassName MyLibrary.dll\n\n# Decompile with specific C# version\nilspycmd -lv CSharp12_0 MyLibrary.dll\n\n# Decompile with reference path for dependencies\nilspycmd -r ./dependencies MyLibrary.dll\n```\n\n### View IL Code\n\n```bash\n# Show IL instead of C#\nilspycmd -il MyLibrary.dll\n\n# Show IL for specific type\nilspycmd -il -t Namespace.ClassName MyLibrary.dll\n```\n\n### List Types\n\n```bash\nilspycmd -l class MyLibrary.dll # List all classes\nilspycmd -l interface MyLibrary.dll # List interfaces\nilspycmd -l struct MyLibrary.dll # List structs\nilspycmd -l enum MyLibrary.dll # List enums\nilspycmd -l delegate MyLibrary.dll # List delegates\n```\n\n## Workflow\n\n1. Identify the API, class, or method to understand\n2. Locate the assembly (NuGet cache, runtime, or build output)\n3. List types to find the exact name: `ilspycmd -l class MyLib.dll`\n4. Decompile the specific type: `ilspycmd -t Full.TypeName MyLib.dll`\n5. If dependencies are missing, add reference paths: `ilspycmd -r ./deps -t Type MyLib.dll`\n\n## Common Scenarios\n\n### Understand how a framework API works\n\n```bash\n# How does JsonSerializer.Serialize work?\nilspycmd -t System.Text.Json.JsonSerializer \\\n ~/.dotnet/shared/Microsoft.NETCore.App/10.0.0/System.Text.Json.dll\n\n# How does WebApplication.CreateBuilder work?\nilspycmd -t Microsoft.AspNetCore.Builder.WebApplication \\\n ~/.dotnet/shared/Microsoft.AspNetCore.App/10.0.0/Microsoft.AspNetCore.dll\n```\n\n### Inspect a NuGet package implementation\n\n```bash\n# Decompile entire package to a project for exploration\nilspycmd -p -o ./polly-src \\\n ~/.nuget/packages/polly.core/8.5.0/lib/net8.0/Polly.Core.dll\n```\n\n### Compare C# and IL\n\n```bash\n# C# view\nilspycmd -t MyNamespace.MyClass MyLibrary.dll\n\n# IL view for the same type (see what the compiler actually generates)\nilspycmd -il -t MyNamespace.MyClass MyLibrary.dll\n```\n\n## C# Language Versions\n\nUse `-lv` to control the C# version used for decompilation output:\n\n| Flag | Version |\n|------|---------|\n| `CSharp1` through `CSharp12_0` | Specific C# version |\n| `Latest` | Latest stable |\n| `Preview` | Preview features |\n\nHigher versions produce more idiomatic code (records, pattern matching, etc.). Lower versions show the expanded form.\n\n## Agent Gotchas\n\n1. **Use `dnx ilspycmd` on .NET 10+** — no global tool install needed. Fall back to `dotnet tool install -g ilspycmd` on older SDKs.\n2. **Reference assemblies are stubs** — files under `packs/Microsoft.NETCore.App.Ref/` are design-time facades with no method bodies. Decompile from `shared/Microsoft.NETCore.App/` instead.\n3. **Dependencies may be needed** — if decompilation shows unresolved types, use `-r` to point to a directory containing the dependency DLLs.\n4. **NuGet cache paths vary by platform** — `~/.nuget/packages/` is the default on all platforms, but can be overridden via `NUGET_PACKAGES` env var.\n5. **Don't decompile to stdout for large assemblies** — use `-o` to write to a directory, or `-t` to target a specific type.\n\n## References\n\n- [ILSpy GitHub](https://github.com/icsharpcode/ILSpy)\n- [ilspycmd NuGet](https://www.nuget.org/packages/ilspycmd)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5031,"content_sha256":"da1f7a38d4f03a4ffea3a24759086cda53e1f761c05054c42e9642e83544dacb"},{"filename":"references/mermaid-diagrams.md","content":"# Mermaid Diagrams\n\nMermaid diagram reference for .NET projects: architecture diagrams (C4-style context, container, component views, layered architecture, microservice topology), sequence diagrams (API request flows, async/await patterns, middleware pipeline, authentication flows), class diagrams (domain models, DI registration graphs, inheritance hierarchies, interface implementations), deployment diagrams (container deployment, Kubernetes pod layout, CI/CD pipeline flow), ER diagrams (EF Core model relationships, database schema visualization), state diagrams (workflow states, order processing, saga patterns, state machine patterns), and flowcharts (decision trees, framework selection, architecture choices). Includes diagram-as-code conventions for naming, grouping, GitHub rendering, and dark mode considerations.\n\n**Version assumptions:** Mermaid v10+ (supported by GitHub, Starlight, Docusaurus natively). GitHub renders Mermaid in Markdown files, issues, PRs, and discussions. .NET 8.0+ baseline for code examples.\n\nFor complete diagram examples, see the Detailed Examples section below.\n\n## Supported Diagram Types\n\n### Architecture Diagrams\n\n- **C4-Style Context** -- system in its environment with external actors (10-12 nodes max)\n- **C4-Style Component** -- internal structure of a single service (15-20 nodes max)\n- **Layered Architecture** -- Presentation, Application, Domain, Infrastructure layers\n- **Microservice Topology** -- services, messaging, observability connections\n\n### Sequence Diagrams\n\n- **API Request Flow** -- HTTP request through middleware, auth, controller, service, database\n- **Async/Await Pattern** -- thread pool behavior, cache miss/hit, await points\n- **Middleware Pipeline** -- request/response flow through ASP.NET Core middleware chain\n- **Authentication Flow** -- OAuth 2.0/OIDC with BFF pattern\n\n### Class Diagrams\n\n- **Domain Model** -- entities, value objects, enumerations, relationships\n- **DI Registration Graph** -- singleton/scoped/transient lifetime visualization\n- **Interface Implementation Hierarchy** -- generic repository pattern with inheritance\n\n### Deployment Diagrams\n\n- **Container Deployment** -- Docker host with app, database, cache, reverse proxy\n- **Kubernetes Pod Layout** -- cluster, namespace, deployments, services, config\n- **CI/CD Pipeline Flow** -- build, test, package, deploy stages\n\n### ER Diagrams\n\n- **EF Core Relationship Visualization** -- one-to-many, one-to-one, many-to-many with entity details\n- **Database Schema with Indexes** -- audit logs, soft delete, multi-tenant patterns\n\n### State Diagrams\n\n- **Order Processing Workflow** -- draft through delivery with payment states\n- **Saga Pattern** -- distributed transaction with compensation steps\n- **State Machine Pattern** -- event-driven state transitions\n\n### Flowcharts\n\n- **Framework Selection Decision Tree** -- web vs desktop, API vs UI, framework choices\n- **Architecture Decision Flowchart** -- monolith vs microservices, communication patterns\n\n\n## Diagram-as-Code Conventions\n\n### Naming Conventions\n\n- Use PascalCase for node IDs: `OrderService`, `CustomerDB`\n- Use descriptive labels with technology: `API[\"Order API\u003cbr/>(ASP.NET Core)\"]`\n- Use consistent abbreviations: DB (database), API (endpoint), SVC (service), MQ (message queue)\n- Prefix subgraphs with the layer or tier name: `subgraph DataTier[\"Data Tier\"]`\n\n### Grouping Patterns\n\n- Group by architectural layer (Presentation, Application, Domain, Infrastructure)\n- Group by deployment boundary (containers, pods, VMs)\n- Group by team ownership in microservice diagrams\n- Use subgraphs for visual grouping -- limit nesting to 2 levels for readability\n\n### GitHub Rendering Tips\n\n- GitHub renders Mermaid in fenced code blocks with the `mermaid` language identifier in Markdown files, issues, PRs, and discussions\n- Maximum recommended diagram size: ~50 nodes for readable rendering\n- GitHub uses a light theme by default -- avoid light-colored fill that disappears on white backgrounds\n- Diagrams auto-size to container width -- keep node labels concise (under 30 characters per line)\n- Use `\u003cbr/>` for line breaks within node labels (not `\\n`)\n- Test diagrams in GitHub before merging -- syntax errors render as raw text\n\n### Dark Mode Considerations\n\n- Use the `neutral` theme (`%%{init: {'theme': 'neutral'}}%%`) for best cross-theme compatibility on GitHub -- it renders well in both light and dark modes\n- Avoid hardcoded colors that fail in dark mode -- use Mermaid theme variables when possible\n- Default Mermaid colors work in both light and dark themes on GitHub\n- If using custom `style` directives, test in both GitHub light and dark modes\n- Prefer semantic `classDef` styles over inline `style` for maintainability\n- For doc sites (Starlight, Docusaurus), Mermaid inherits the platform's CSS theme automatically\n\n### Diagram Size Guidelines\n\n| Diagram Type | Recommended Max Nodes | Notes |\n|---|---|---|\n| C4 Context | 10-12 | One system + external actors |\n| C4 Component | 15-20 | Single service internals |\n| Sequence | 8 participants | More becomes unreadable |\n| Class | 10-15 classes | Split into multiple diagrams |\n| ER | 10-12 entities | Split by bounded context |\n| State | 12-15 states | Split complex workflows |\n| Flowchart | 15-20 nodes | Keep decision trees focused |\n\n\n## Agent Gotchas\n\n1. **Always use `.NET-specific content` in diagrams** -- do not generate generic diagrams. Use real .NET types (DbContext, IRepository, IMessageBus), real .NET tools (EF Core, Wolverine, YARP), and real .NET patterns (middleware pipeline, DI registration).\n\n2. **ER diagram relationship notation follows Mermaid syntax, not UML** -- use `||--o{` for one-to-many, `||--||` for one-to-one.\n\n3. **Sequence diagram participant names cannot contain special characters** -- use `participant DB as \"SQL Server\"` alias syntax for names with spaces or special characters.\n\n4. **Nested generics (`Task~List~T~~`) require Mermaid v10.3+** -- test rendering before committing complex generic type diagrams.\n\n5. **Do not use Font Awesome icon syntax (`fa:fa-user`) in GitHub diagrams** -- GitHub's Mermaid renderer does not load Font Awesome. Icons render as literal text.\n\n---\n\n# Mermaid Diagrams -- Diagram Examples\n\nComplete Mermaid diagram examples for .NET projects. Each section provides ready-to-use diagram templates with .NET-specific content.\n\n---\n\n## Architecture Diagrams\n\n### C4-Style Context Diagram\n\nShows the system in its environment with external actors and systems.\n\n```mermaid\ngraph TB\n User[\"End User\u003cbr/>(Browser/Mobile)\"]\n Admin[\"Admin\u003cbr/>(Internal)\"]\n\n subgraph System[\"My .NET Application\"]\n API[\"ASP.NET Core API\u003cbr/>(.NET 8)\"]\n end\n\n ExtAuth[\"Identity Provider\u003cbr/>(Azure AD / Auth0)\"]\n ExtEmail[\"Email Service\u003cbr/>(SendGrid)\"]\n ExtPay[\"Payment Gateway\u003cbr/>(Stripe)\"]\n\n User -->|\"HTTPS\"| API\n Admin -->|\"HTTPS\"| API\n API -->|\"OAuth 2.0\"| ExtAuth\n API -->|\"SMTP/API\"| ExtEmail\n API -->|\"REST API\"| ExtPay\n```\n\n### C4-Style Component Diagram\n\nShows internal structure of a single service.\n\n```mermaid\ngraph TB\n subgraph OrderService[\"Order Service\"]\n Controllers[\"Controllers\u003cbr/>(API Endpoints)\"]\n Validators[\"FluentValidation\u003cbr/>(Request Validators)\"]\n Handlers[\"Command Handlers\u003cbr/>(Business Logic)\"]\n DomainModels[\"Domain Models\u003cbr/>(Entities, Value Objects)\"]\n Repos[\"Repositories\u003cbr/>(EF Core)\"]\n Events[\"Domain Events\u003cbr/>(Notifications)\"]\n IntEvents[\"Integration Events\u003cbr/>(Wolverine)\"]\n end\n\n Controllers --> Validators\n Controllers --> Handlers\n Handlers --> DomainModels\n Handlers --> Repos\n Handlers --> Events\n Events --> IntEvents\n```\n\n### Layered Architecture\n\n```mermaid\ngraph TB\n subgraph Presentation[\"Presentation Layer\"]\n API[\"ASP.NET Core Controllers / Minimal APIs\"]\n Blazor[\"Blazor Components\"]\n end\n\n subgraph Application[\"Application Layer\"]\n Services[\"Application Services\"]\n DTOs[\"DTOs / View Models\"]\n Mappings[\"Mapperly Mappers\"]\n CQRS[\"Mediator Handlers\"]\n end\n\n subgraph Domain[\"Domain Layer\"]\n Entities[\"Entities\"]\n ValueObjects[\"Value Objects\"]\n DomainEvents[\"Domain Events\"]\n Interfaces[\"Repository Interfaces\"]\n end\n\n subgraph Infrastructure[\"Infrastructure Layer\"]\n EFCore[\"EF Core DbContext\"]\n Repositories[\"Repository Implementations\"]\n ExternalServices[\"External Service Clients\"]\n Messaging[\"Wolverine / RabbitMQ\"]\n end\n\n Presentation --> Application\n Application --> Domain\n Infrastructure --> Domain\n Infrastructure -.->|\"implements\"| Interfaces\n```\n\n### Microservice Topology\n\n```mermaid\ngraph LR\n subgraph Ingress\n LB[\"Load Balancer\"]\n GW[\"API Gateway\u003cbr/>(YARP)\"]\n end\n\n subgraph Services\n S1[\"Order Service\"]\n S2[\"Catalog Service\"]\n S3[\"Identity Service\"]\n S4[\"Notification Service\"]\n end\n\n subgraph Messaging\n MQ[\"RabbitMQ\"]\n end\n\n subgraph Observability\n SEQ[\"Seq / ELK\"]\n OTEL[\"OpenTelemetry Collector\"]\n end\n\n LB --> GW\n GW --> S1\n GW --> S2\n GW --> S3\n S1 -->|\"publish\"| MQ\n MQ -->|\"subscribe\"| S2\n MQ -->|\"subscribe\"| S4\n S1 -.->|\"traces\"| OTEL\n S2 -.->|\"traces\"| OTEL\n S1 -.->|\"logs\"| SEQ\n S2 -.->|\"logs\"| SEQ\n```\n\n---\n\n## Sequence Diagrams\n\n### API Request Flow\n\n```mermaid\nsequenceDiagram\n participant Client\n participant Middleware as ASP.NET Middleware\n participant Auth as Authentication\n participant Controller\n participant Service\n participant DB as Database\n\n Client->>Middleware: POST /api/orders\n Middleware->>Auth: Validate JWT\n Auth-->>Middleware: Claims Principal\n Middleware->>Controller: OrdersController.Create()\n Controller->>Service: CreateOrderAsync(dto)\n Service->>DB: INSERT INTO Orders\n DB-->>Service: Order entity\n Service-->>Controller: OrderResponse\n Controller-->>Client: 201 Created\n```\n\n### Async/Await Pattern\n\n```mermaid\nsequenceDiagram\n participant Caller\n participant Service as OrderService\n participant Repo as IOrderRepository\n participant DB as SQL Server\n participant Cache as Redis\n\n Caller->>+Service: GetOrderAsync(id)\n Service->>+Cache: GetAsync(key)\n Cache-->>-Service: null (cache miss)\n Service->>+Repo: FindByIdAsync(id)\n Repo->>+DB: SELECT ... WHERE Id = @id\n Note over Repo,DB: await - thread returned to pool\n DB-->>-Repo: Row data\n Repo-->>-Service: Order entity\n Service->>+Cache: SetAsync(key, order, expiry)\n Note over Service,Cache: Fire-and-forget or await\n Cache-->>-Service: OK\n Service-->>-Caller: Order\n```\n\n### Middleware Pipeline\n\n```mermaid\nsequenceDiagram\n participant Client\n participant ExHandler as ExceptionHandler\n participant HSTS as HSTS Middleware\n participant Auth as Authentication\n participant Authz as Authorization\n participant CORS as CORS Middleware\n participant Routing as Routing\n participant Endpoint\n\n Client->>ExHandler: HTTP Request\n ExHandler->>HSTS: next()\n HSTS->>Auth: next()\n Auth->>Authz: next()\n Authz->>CORS: next()\n CORS->>Routing: next()\n Routing->>Endpoint: Matched endpoint\n Endpoint-->>Routing: Response\n Routing-->>CORS: Response\n CORS-->>Authz: Response\n Authz-->>Auth: Response\n Auth-->>HSTS: Response\n HSTS-->>ExHandler: Response\n ExHandler-->>Client: HTTP Response\n```\n\n### Authentication Flow (OAuth 2.0 / OIDC)\n\n```mermaid\nsequenceDiagram\n participant User\n participant App as Blazor App\n participant BFF as BFF (ASP.NET Core)\n participant IDP as Identity Provider\n participant API as Protected API\n\n User->>App: Navigate to protected page\n App->>BFF: GET /api/user (no cookie)\n BFF-->>App: 401 Unauthorized\n App->>BFF: GET /login\n BFF->>IDP: Authorization Code + PKCE\n IDP->>User: Login page\n User->>IDP: Credentials\n IDP->>BFF: Authorization code\n BFF->>IDP: Exchange code for tokens\n IDP-->>BFF: Access + Refresh + ID tokens\n BFF-->>App: Set-Cookie (session)\n App->>BFF: GET /api/orders (with cookie)\n BFF->>API: GET /api/orders (Bearer token)\n API-->>BFF: Order data\n BFF-->>App: Order data\n```\n\n---\n\n## Class Diagrams\n\n### Domain Model\n\n```mermaid\nclassDiagram\n class Order {\n +Guid Id\n +DateTime CreatedAt\n +OrderStatus Status\n +decimal TotalAmount\n +List~OrderLine~ Lines\n +AddLine(product, quantity)\n +Submit()\n +Cancel(reason)\n }\n\n class OrderLine {\n +Guid Id\n +Guid ProductId\n +string ProductName\n +int Quantity\n +decimal UnitPrice\n +decimal LineTotal\n }\n\n class OrderStatus {\n \u003c\u003cenumeration>>\n Draft\n Submitted\n Processing\n Shipped\n Delivered\n Cancelled\n }\n\n class Customer {\n +Guid Id\n +string Name\n +string Email\n +Address ShippingAddress\n +List~Order~ Orders\n }\n\n class Address {\n \u003c\u003cvalue object>>\n +string Street\n +string City\n +string State\n +string PostalCode\n +string Country\n }\n\n Order \"1\" --> \"*\" OrderLine : contains\n Order --> OrderStatus : has\n Customer \"1\" --> \"*\" Order : places\n Customer --> Address : has\n```\n\n### DI Registration Graph\n\n```mermaid\ngraph TB\n subgraph Singleton\n S1[\"IConfiguration\"]\n S2[\"IMemoryCache\"]\n S3[\"IHttpClientFactory\"]\n end\n\n subgraph Scoped[\"Scoped (per-request)\"]\n SC1[\"DbContext\"]\n SC2[\"IOrderRepository\"]\n SC3[\"ICurrentUser\"]\n end\n\n subgraph Transient\n T1[\"IValidator~CreateOrderCommand~\"]\n T2[\"INotificationSender\"]\n end\n\n SC2 -->|\"depends on\"| SC1\n SC2 -->|\"depends on\"| S2\n T2 -->|\"depends on\"| S3\n SC3 -->|\"depends on\"| S1\n\n style Singleton fill:#e1f5fe\n style Scoped fill:#f3e5f5\n style Transient fill:#fff3e0\n```\n\n### Interface Implementation Hierarchy\n\n```mermaid\nclassDiagram\n class IRepository~T~ {\n \u003c\u003cinterface>>\n +GetByIdAsync(id) Task~T~\n +ListAsync() Task~List~T~~\n +AddAsync(entity) Task\n +UpdateAsync(entity) Task\n +DeleteAsync(id) Task\n }\n\n class IOrderRepository {\n \u003c\u003cinterface>>\n +GetByCustomerAsync(customerId) Task~List~Order~~\n +GetPendingAsync() Task~List~Order~~\n }\n\n class RepositoryBase~T~ {\n \u003c\u003cabstract>>\n #DbContext _context\n +GetByIdAsync(id) Task~T~\n +ListAsync() Task~List~T~~\n +AddAsync(entity) Task\n +UpdateAsync(entity) Task\n +DeleteAsync(id) Task\n }\n\n class OrderRepository {\n +GetByCustomerAsync(customerId) Task~List~Order~~\n +GetPendingAsync() Task~List~Order~~\n }\n\n IRepository~T~ \u003c|.. RepositoryBase~T~ : implements\n IOrderRepository \u003c|.. OrderRepository : implements\n RepositoryBase~T~ \u003c|-- OrderRepository : extends\n IRepository~T~ \u003c|-- IOrderRepository : extends\n```\n\n---\n\n## Deployment Diagrams\n\n### Container Deployment\n\n```mermaid\ngraph TB\n subgraph Host[\"Docker Host / VM\"]\n subgraph AppContainer[\"App Container\"]\n App[\"ASP.NET Core App\u003cbr/>mcr.microsoft.com/dotnet/aspnet:8.0\"]\n end\n\n subgraph DBContainer[\"Database Container\"]\n DB[\"SQL Server\u003cbr/>mcr.microsoft.com/mssql/server:2022\"]\n end\n\n subgraph CacheContainer[\"Cache Container\"]\n Redis[\"Redis\u003cbr/>redis:7-alpine\"]\n end\n\n subgraph ReverseProxy[\"Reverse Proxy\"]\n Nginx[\"Nginx\u003cbr/>(SSL termination)\"]\n end\n end\n\n Internet[\"Internet\"] -->|\"443\"| Nginx\n Nginx -->|\"8080\"| App\n App -->|\"1433\"| DB\n App -->|\"6379\"| Redis\n```\n\n### Kubernetes Pod Layout\n\n```mermaid\ngraph TB\n subgraph Cluster[\"Kubernetes Cluster\"]\n subgraph NS[\"namespace: myapp\"]\n subgraph Deploy1[\"Deployment: order-api\"]\n Pod1A[\"Pod\u003cbr/>order-api\u003cbr/>(replica 1)\"]\n Pod1B[\"Pod\u003cbr/>order-api\u003cbr/>(replica 2)\"]\n end\n\n subgraph Deploy2[\"Deployment: catalog-api\"]\n Pod2A[\"Pod\u003cbr/>catalog-api\u003cbr/>(replica 1)\"]\n end\n\n SVC1[\"Service: order-api-svc\u003cbr/>ClusterIP:80\"]\n SVC2[\"Service: catalog-api-svc\u003cbr/>ClusterIP:80\"]\n Ingress[\"Ingress Controller\u003cbr/>(nginx)\"]\n CM[\"ConfigMap:\u003cbr/>appsettings\"]\n Secret[\"Secret:\u003cbr/>connection-strings\"]\n end\n end\n\n Internet[\"Internet\"] --> Ingress\n Ingress -->|\"/api/orders\"| SVC1\n Ingress -->|\"/api/catalog\"| SVC2\n SVC1 --> Pod1A\n SVC1 --> Pod1B\n SVC2 --> Pod2A\n CM -.->|\"mount\"| Pod1A\n CM -.->|\"mount\"| Pod2A\n Secret -.->|\"mount\"| Pod1A\n Secret -.->|\"mount\"| Pod2A\n```\n\n### CI/CD Pipeline Flow\n\n```mermaid\ngraph LR\n subgraph Trigger\n Push[\"Push to main\"]\n PR[\"Pull Request\"]\n Tag[\"Version Tag\"]\n end\n\n subgraph Build[\"Build Stage\"]\n Restore[\"dotnet restore\"]\n Compile[\"dotnet build\"]\n Test[\"dotnet test\"]\n end\n\n subgraph Package[\"Package Stage\"]\n Pack[\"dotnet pack\"]\n Publish[\"dotnet publish\"]\n Docker[\"docker build\"]\n end\n\n subgraph Deploy[\"Deploy Stage\"]\n NuGet[\"Push to NuGet\"]\n ACR[\"Push to ACR\"]\n Staging[\"Deploy to Staging\"]\n Prod[\"Deploy to Production\"]\n end\n\n Push --> Restore\n PR --> Restore\n Tag --> Restore\n Restore --> Compile --> Test\n Test --> Pack\n Test --> Publish --> Docker\n Pack -->|\"tag only\"| NuGet\n Docker --> ACR\n ACR --> Staging\n Staging -->|\"approval\"| Prod\n```\n\n---\n\n## ER Diagrams (EF Core Models)\n\n### EF Core Relationship Visualization\n\n```mermaid\nerDiagram\n Customer ||--o{ Order : places\n Order ||--|{ OrderLine : contains\n OrderLine }o--|| Product : references\n Product }o--|| Category : \"belongs to\"\n Order ||--o| ShippingAddress : \"ships to\"\n Customer ||--o| CustomerProfile : has\n\n Customer {\n guid Id PK\n string Name\n string Email UK\n datetime CreatedAt\n }\n\n Order {\n guid Id PK\n guid CustomerId FK\n datetime OrderDate\n string Status\n decimal TotalAmount\n }\n\n OrderLine {\n guid Id PK\n guid OrderId FK\n guid ProductId FK\n int Quantity\n decimal UnitPrice\n }\n\n Product {\n guid Id PK\n guid CategoryId FK\n string Name\n string SKU UK\n decimal Price\n int StockQuantity\n }\n\n Category {\n guid Id PK\n string Name UK\n string Description\n guid ParentCategoryId FK \"nullable, self-ref\"\n }\n\n ShippingAddress {\n guid Id PK\n guid OrderId FK \"unique\"\n string Street\n string City\n string PostalCode\n string Country\n }\n\n CustomerProfile {\n guid Id PK\n guid CustomerId FK \"unique\"\n string AvatarUrl\n string Bio\n }\n```\n\n### Database Schema with Indexes\n\n```mermaid\nerDiagram\n AuditLog {\n long Id PK \"identity\"\n string EntityType \"indexed\"\n guid EntityId \"indexed\"\n string Action\n string UserId FK \"indexed\"\n jsonb Changes\n datetime Timestamp \"indexed, default GETUTCDATE()\"\n }\n\n SoftDeleteEntity {\n guid Id PK\n string Name\n bool IsDeleted \"global query filter\"\n datetime DeletedAt \"nullable\"\n string DeletedBy \"nullable\"\n }\n\n TenantEntity {\n guid Id PK\n guid TenantId FK \"global query filter, indexed\"\n string Data\n }\n```\n\n---\n\n## State Diagrams\n\n### Order Processing Workflow\n\n```mermaid\nstateDiagram-v2\n [*] --> Draft : Create order\n\n Draft --> Submitted : Submit()\n Draft --> Cancelled : Cancel()\n\n Submitted --> PaymentPending : Process payment\n Submitted --> Cancelled : Cancel()\n\n PaymentPending --> PaymentFailed : Payment declined\n PaymentPending --> Paid : Payment confirmed\n\n PaymentFailed --> PaymentPending : Retry payment\n PaymentFailed --> Cancelled : Cancel()\n\n Paid --> Processing : Begin fulfillment\n Processing --> Shipped : Ship order\n Shipped --> Delivered : Confirm delivery\n\n Delivered --> [*]\n Cancelled --> [*]\n\n note right of PaymentPending\n Timeout after 30 minutes\n auto-transitions to PaymentFailed\n end note\n```\n\n### Saga Pattern (Distributed Transaction)\n\n```mermaid\nstateDiagram-v2\n [*] --> OrderCreated : Start saga\n\n OrderCreated --> InventoryReserved : ReserveInventory\n OrderCreated --> OrderFailed : ReserveInventory failed\n\n InventoryReserved --> PaymentProcessed : ProcessPayment\n InventoryReserved --> InventoryReleased : ProcessPayment failed\n\n InventoryReleased --> OrderFailed : Compensate\n\n PaymentProcessed --> ShipmentScheduled : ScheduleShipment\n PaymentProcessed --> PaymentRefunded : ScheduleShipment failed\n\n PaymentRefunded --> InventoryReleased : Compensate\n\n ShipmentScheduled --> OrderCompleted : All steps succeeded\n OrderCompleted --> [*]\n OrderFailed --> [*]\n\n note left of InventoryReleased\n Compensation step:\n release reserved stock\n end note\n\n note left of PaymentRefunded\n Compensation step:\n refund payment\n end note\n```\n\n### State Machine Pattern (Event-Driven)\n\n```mermaid\nstateDiagram-v2\n [*] --> New\n\n state \"Awaiting Validation\" as Validating\n state \"Awaiting Payment\" as AwaitingPayment\n state \"Payment Failed\" as PaymentFailed\n\n New --> Validating : OrderSubmitted event\n Validating --> AwaitingPayment : OrderValidated event\n Validating --> Cancelled : ValidationFailed event\n\n AwaitingPayment --> Confirmed : PaymentReceived event\n AwaitingPayment --> PaymentFailed : PaymentDeclined event\n\n PaymentFailed --> AwaitingPayment : PaymentRetried event\n PaymentFailed --> Cancelled : MaxRetriesExceeded\n\n Confirmed --> Shipped : OrderShipped event\n Shipped --> Completed : OrderDelivered event\n\n Completed --> [*]\n Cancelled --> [*]\n```\n\n---\n\n## Flowcharts\n\n### Framework Selection Decision Tree\n\n```mermaid\nflowchart TD\n Start[\"New .NET Project\"] --> WebOrDesktop{\"Web or Desktop?\"}\n\n WebOrDesktop -->|\"Web\"| APIOnly{\"API only?\"}\n WebOrDesktop -->|\"Desktop\"| DesktopPlatform{\"Target platforms?\"}\n\n APIOnly -->|\"Yes\"| MinimalOrMVC{\"Preference?\"}\n APIOnly -->|\"No, needs UI\"| UIFramework{\"Server or Client rendering?\"}\n\n MinimalOrMVC -->|\"Simple, few endpoints\"| MinimalAPI[\"Minimal APIs\"]\n MinimalOrMVC -->|\"Complex, many controllers\"| MVC[\"ASP.NET Core MVC/API\"]\n\n UIFramework -->|\"Server\"| BlazorServer[\"Blazor Server / SSR\"]\n UIFramework -->|\"Client\"| BlazorWASM[\"Blazor WebAssembly\"]\n UIFramework -->|\"Both\"| BlazorAuto[\"Blazor Auto (SSR + WASM)\"]\n\n DesktopPlatform -->|\"Windows only\"| WPFOrWinUI{\"Modern UI needed?\"}\n DesktopPlatform -->|\"Cross-platform\"| MAUI[\"MAUI\"]\n\n WPFOrWinUI -->|\"Legacy compat\"| WPF[\"WPF\"]\n WPFOrWinUI -->|\"Modern\"| WinUI[\"WinUI 3\"]\n```\n\n### Architecture Decision Flowchart\n\n```mermaid\nflowchart TD\n Start[\"Service Design Decision\"] --> Scale{\"Expected scale?\"}\n\n Scale -->|\"Single team, moderate load\"| Monolith[\"Modular Monolith\"]\n Scale -->|\"Multiple teams, high load\"| Micro[\"Microservices\"]\n\n Monolith --> MonoComm{\"Communication pattern?\"}\n MonoComm -->|\"In-process\"| Mediator[\"Mediator + Vertical Slices\"]\n MonoComm -->|\"Async events\"| MonoBus[\"Wolverine (in-memory)\"]\n\n Micro --> MicroComm{\"Communication pattern?\"}\n MicroComm -->|\"Synchronous\"| gRPC[\"gRPC / REST\"]\n MicroComm -->|\"Asynchronous\"| MsgBus[\"Message Bus\u003cbr/>(RabbitMQ / Azure SB)\"]\n MicroComm -->|\"Both\"| Hybrid[\"gRPC + Message Bus\"]\n\n Micro --> DataStrategy{\"Data strategy?\"}\n DataStrategy -->|\"Shared DB\"| SharedDB[\"Shared Database\u003cbr/>(simpler, less isolation)\"]\n DataStrategy -->|\"DB per service\"| OwnDB[\"Database per Service\u003cbr/>(more isolation, eventual consistency)\"]\n```\n\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":24081,"content_sha256":"28b2f5d6a3b3fa97ac5ec50c463dd844370dc2a0951949deda70f5de096532d4"},{"filename":"references/modernize.md","content":"# Modernize\n\nAnalyze existing .NET code for modernization opportunities. Identifies outdated target frameworks, deprecated packages, superseded API patterns, and missing modern best practices. Provides actionable recommendations for each finding.\n\n## Modernization Checklist\n\nRun through this checklist against the existing codebase. Each section identifies what to look for and what the modern replacement is.\n\n### 1. Target Framework\n\nCheck `\u003cTargetFramework>` in `.csproj` files (or `Directory.Build.props`):\n\n| Current TFM | Status | Recommendation |\n|-------------|--------|----------------|\n| `net8.0` | LTS -- supported until Nov 2026 | Plan upgrade to `net10.0` (LTS) |\n| `net9.0` | STS -- support ends May 2026 | Upgrade to `net10.0` promptly |\n| `net7.0` | End of life | Upgrade immediately |\n| `net6.0` | End of life | Upgrade immediately |\n| `net5.0` or lower | End of life | Upgrade immediately |\n| `netstandard2.0/2.1` | Supported (library compat) | Keep if multi-targeting for broad reach |\n| `netcoreapp3.1` | End of life | Upgrade immediately |\n| `.NET Framework 4.x` | Legacy | Evaluate migration feasibility |\n\nTo scan all projects:\n\n```bash\n# Find all TFMs in the solution\nfind . -name \"*.csproj\" -exec grep -h \"TargetFramework\" {} \\; | sort -u\n\n# Check Directory.Build.props\ngrep \"TargetFramework\" Directory.Build.props 2>/dev/null\n```\n\n\n### 2. Deprecated and Superseded Packages\n\nScan `Directory.Packages.props` (or individual `.csproj` files) for packages that have been superseded:\n\n| Deprecated Package | Replacement | Since |\n|-------------------|-------------|-------|\n| `Microsoft.Extensions.Http.Polly` | `Microsoft.Extensions.Http.Resilience` | .NET 8 |\n| `Newtonsoft.Json` (new projects) | `System.Text.Json` | .NET Core 3.0+ |\n| `Microsoft.AspNetCore.Mvc.NewtonsoftJson` | Built-in STJ | .NET Core 3.0+ |\n| `Swashbuckle.AspNetCore` | Built-in OpenAPI (`Microsoft.AspNetCore.OpenApi`) for document generation; keep Swashbuckle if using Swagger UI, filters, or codegen | .NET 9 |\n| `NSwag.AspNetCore` | Built-in OpenAPI for document generation; keep NSwag if using client generation or Swagger UI features | .NET 9 |\n| `Microsoft.Extensions.Logging.Log4Net.AspNetCore` | Built-in logging + `Serilog` or `OpenTelemetry` | .NET Core 2.0+ |\n| `Microsoft.AspNetCore.Authentication.JwtBearer` (explicit NuGet package) | Remove explicit PackageReference — included in `Microsoft.AspNetCore.App` shared framework | .NET Core 3.0+ |\n| `System.Data.SqlClient` | `Microsoft.Data.SqlClient` | .NET Core 3.0+ |\n| `Microsoft.Azure.Storage.*` | `Azure.Storage.*` | 2020+ |\n| `WindowsAzure.Storage` | `Azure.Storage.Blobs` / `Azure.Storage.Queues` | 2020+ |\n| `Microsoft.Azure.ServiceBus` | `Azure.Messaging.ServiceBus` | 2020+ |\n| `Microsoft.Azure.EventHubs` | `Azure.Messaging.EventHubs` | 2020+ |\n| `EntityFramework` (EF6) | `Microsoft.EntityFrameworkCore` | .NET Core 1.0+ |\n| `RestSharp` (older versions) | `HttpClient` + `System.Text.Json` | .NET Core+ |\n| `AutoMapper` | Manual mapping or source-generated mappers | Preference |\n\nTo scan for deprecated packages:\n\n```bash\n# List all package references\ngrep -rh \"PackageVersion\\|PackageReference\" \\\n Directory.Packages.props $(find . -name \"*.csproj\") 2>/dev/null | \\\n grep -i \"Include=\" | sort -u\n```\n\n**Note on Newtonsoft.Json:** Existing projects with deep Newtonsoft.Json usage (custom converters, `JObject` manipulation) may not benefit from immediate migration. Flag it but assess the migration cost.\n\n\n### 3. Superseded API Patterns\n\nLook for code patterns that have modern replacements:\n\n#### Startup.cs / Program.cs Pattern\n\n**Old (pre-.NET 6):**\n```csharp\npublic class Startup\n{\n public void ConfigureServices(IServiceCollection services) { }\n public void Configure(IApplicationBuilder app) { }\n}\n```\n\n**Modern (minimal hosting):**\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\n// ConfigureServices equivalent\nvar app = builder.Build();\n// Configure equivalent\napp.Run();\n```\n\n#### HttpClient Registration\n\n**Old:**\n```csharp\nservices.AddHttpClient\u003cMyService>(client =>\n{\n client.BaseAddress = new Uri(\"https://api.example.com\");\n})\n.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(300)));\n```\n\n**Modern (with Microsoft.Extensions.Resilience):**\n```csharp\nservices.AddHttpClient\u003cMyService>(client =>\n{\n client.BaseAddress = new Uri(\"https://api.example.com\");\n})\n.AddStandardResilienceHandler();\n```\n\n#### Synchronous I/O\n\n**Flag:** `File.ReadAllText`, `Stream.Read`, `HttpClient` without `Async` suffix.\n\n**Modern:** Use `async` variants -- `File.ReadAllTextAsync`, `Stream.ReadAsync`, `await httpClient.GetAsync()`.\n\n#### String Concatenation in Hot Paths\n\n**Flag:** String concatenation (`+`) or `String.Format` in logging, loops.\n\n**Modern:** Use string interpolation with `LoggerMessage` source generators, or `StringBuilder`.\n\n#### Legacy Collection Patterns\n\n**Flag:** `Hashtable`, `ArrayList`, non-generic collections.\n\n**Modern:** `Dictionary\u003cTKey, TValue>`, `List\u003cT>`, generic collections.\n\n#### ILogger Pattern\n\n**Old:**\n```csharp\n_logger.LogInformation(\"Processing order {OrderId}\", orderId);\n```\n\n**Modern (high-performance):**\n```csharp\n[LoggerMessage(Level = LogLevel.Information, Message = \"Processing order {OrderId}\")]\nstatic partial void LogProcessingOrder(ILogger logger, string orderId);\n```\n\n\n### 4. Missing Modern Build Configuration\n\nCheck for the absence of recommended build infrastructure:\n\n| Missing | Check | Recommendation |\n|---------|-------|----------------|\n| Central Package Management | No `Directory.Packages.props` | See `references/project-structure.md` |\n| Directory.Build.props | Properties scattered across `.csproj` files | Centralize shared properties |\n| .editorconfig | No `.editorconfig` at repo root | See `references/project-structure.md` |\n| global.json | No SDK pinning | Add for reproducible builds |\n| NuGet audit | No `NuGetAudit` property | Enable in `Directory.Build.props` |\n| Lock files | No `RestorePackagesWithLockFile` | Enable for deterministic restores |\n| Package source mapping | No `packageSourceMapping` in `nuget.config` | Add for supply-chain security |\n| Analyzers | No `AnalysisLevel` or `EnforceCodeStyleInBuild` | See `references/add-analyzers.md` |\n| SourceLink | No SourceLink package reference | Add for debugger source navigation |\n| Nullable reference types | `\u003cNullable>` not enabled | Enable globally |\n| .slnx | Still using `.sln` with .NET 9+ SDK | Migrate with `dotnet sln migrate` |\n\n\n### 5. Deprecated C# Language Patterns\n\n| Old Pattern | Modern Replacement | Language Version |\n|------------|-------------------|-----------------|\n| `switch` statement with `case` | `switch` expression | C# 8 |\n| `null != x` / `x != null` checks | `x is not null` | C# 9 |\n| `new ClassName()` with obvious type | Target-typed `new()` | C# 9 |\n| Block-scoped namespaces | File-scoped namespaces | C# 10 |\n| `record class` explicit constructor | `record` with positional parameters | C# 10 |\n| Manual string concatenation for multi-line | Raw string literals (`\"\"\"...\"\"\"`) | C# 11 |\n| Explicit interface dispatch for `INumber\u003cT>` | Generic math interfaces | C# 11 |\n| `[Flags]` enum manual checks | Improved enum pattern matching | C# 11+ |\n| Lambda without natural type | Natural function types | C# 10+ |\n| `ValueTask` manual wrapping | `Task`/`ValueTask` with `ConfigureAwait` patterns | C# all |\n| Primary constructor classes (manual) | Primary constructors on `class`/`struct` | C# 12 |\n| Multiple `if`/`else if` type checks | `switch` on type with list patterns | C# 11+ |\n| `params T[]` | `params ReadOnlySpan\u003cT>`, `params` collections | C# 13 |\n| Lock with `object` | `System.Threading.Lock` | C# 13 |\n\n\n### 6. Security and Compliance\n\n| Issue | Detection | Fix |\n|-------|-----------|-----|\n| Known vulnerabilities | `dotnet list package --vulnerable` | Update affected packages |\n| Deprecated packages | `dotnet list package --deprecated` | Replace with successors |\n| Outdated packages | `dotnet list package --outdated` | Evaluate updates |\n| Missing HTTPS redirection | No `app.UseHttpsRedirection()` | Add to pipeline |\n| Missing HSTS | No `app.UseHsts()` | Add for production |\n| Hardcoded secrets | Connection strings in `appsettings.json` | Use User Secrets or Key Vault |\n\n```bash\n# Run all NuGet audits\ndotnet list package --vulnerable --include-transitive\ndotnet list package --deprecated\ndotnet list package --outdated\n```\n\n\n## Running a Modernization Scan\n\nCombine the checks into a systematic scan:\n\n```bash\n# 1. Check TFMs\necho \"=== Target Frameworks ===\"\nfind . -name \"*.csproj\" -exec grep -Hl \"TargetFramework\" {} \\; | while read f; do\n echo \"$f: $(grep -o '\u003cTargetFramework[s]*>[^\u003c]*' \"$f\" | head -1)\"\ndone\n\n# 2. Check for deprecated packages\necho \"=== Package Audit ===\"\ndotnet list package --deprecated 2>/dev/null\ndotnet list package --vulnerable --include-transitive 2>/dev/null\n\n# 3. Check build infrastructure\necho \"=== Build Infrastructure ===\"\ntest -f Directory.Build.props && echo \"OK: Directory.Build.props\" || echo \"MISSING: Directory.Build.props\"\ntest -f Directory.Packages.props && echo \"OK: Directory.Packages.props (CPM)\" || echo \"MISSING: Directory.Packages.props\"\ntest -f .editorconfig && echo \"OK: .editorconfig\" || echo \"MISSING: .editorconfig\"\ntest -f global.json && echo \"OK: global.json\" || echo \"MISSING: global.json\"\ntest -f nuget.config && echo \"OK: nuget.config\" || echo \"MISSING: nuget.config\"\n\n# 4. Check for old patterns in code\necho \"=== Code Patterns ===\"\ngrep -rl \"class Startup\" --include=\"*.cs\" . 2>/dev/null && echo \"FOUND: Legacy Startup.cs pattern\"\ngrep -rl \"Microsoft.Extensions.Http.Polly\" --include=\"*.csproj\" --include=\"*.props\" . 2>/dev/null && echo \"FOUND: Deprecated Polly package\"\ngrep -rl \"Swashbuckle\" --include=\"*.csproj\" --include=\"*.props\" . 2>/dev/null && echo \"FOUND: Swashbuckle (consider built-in OpenAPI for .NET 9+)\"\ngrep -rl \"System.Data.SqlClient\" --include=\"*.csproj\" --include=\"*.props\" . 2>/dev/null && echo \"FOUND: System.Data.SqlClient (use Microsoft.Data.SqlClient)\"\n```\n\n\n## Prioritizing Modernization\n\nNot all modernization is equally urgent. Prioritize by impact:\n\n1. **Security** -- vulnerable packages, end-of-life TFMs (no security patches)\n2. **Supportability** -- deprecated packages with no upstream maintenance\n3. **Performance** -- patterns with significant perf impact (sync-over-async, legacy collections in hot paths)\n4. **Developer experience** -- build infrastructure (CPM, analyzers, editorconfig) improves daily workflow\n5. **Code style** -- language pattern updates are lowest priority but reduce cognitive load over time\n\n\n## What's Next\n\nThis skill flags modernization opportunities. For executing upgrades:\n- **TFM version upgrades and migration paths** -- `references/version-upgrade.md`\n- **Multi-targeting strategies** -- `references/multi-targeting.md`\n- **Polyfill packages for cross-version support** -- `references/multi-targeting.md`\n- **Adding missing build infrastructure** -- `references/project-structure.md`, `references/scaffold-project.md`\n- **Configuring analyzers** -- `references/add-analyzers.md`\n- **Adding CI/CD** -- [skill:dotnet-devops]\n\n\n## References\n\n- [.NET Support Policy](https://dotnet.microsoft.com/platform/support/policy/dotnet-core)\n- [Breaking Changes in .NET](https://learn.microsoft.com/en-us/dotnet/core/compatibility/breaking-changes)\n- [.NET Upgrade Assistant](https://learn.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview)\n- [NuGet Package Vulnerability Auditing](https://learn.microsoft.com/en-us/nuget/concepts/auditing-packages)\n- [Modern C# Features](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":11708,"content_sha256":"e143c11c489f2b9c561b8586a18eb6849be92ddc87898f3ff0a4781f9fb24932"},{"filename":"references/msbuild-authoring.md","content":"# MSBuild Authoring\n\nGuidance for authoring MSBuild project system elements: custom targets with `BeforeTargets`/`AfterTargets`/`DependsOnTargets`, incremental build with `Inputs`/`Outputs`, props vs targets import ordering, items and item metadata (`Include`/`Exclude`/`Update`/`Remove`), conditions, property functions, well-known metadata, and advanced `Directory.Build.props`/`Directory.Build.targets` patterns.\n\n**Version assumptions:** .NET 8.0+ SDK (MSBuild 17.8+). All examples use SDK-style projects.\n\n## Custom Targets\n\nTargets are the unit of execution in MSBuild. Each target runs a sequence of tasks and can declare ordering relationships with other targets.\n\n### Defining a Custom Target\n\n```xml\n\u003cTarget Name=\"PrintBuildInfo\"\n BeforeTargets=\"Build\">\n \u003cMessage Importance=\"high\"\n Text=\"Building $(MSBuildProjectName) v$(Version) for $(TargetFramework)\" />\n\u003c/Target>\n```\n\n### Ordering: BeforeTargets, AfterTargets, DependsOnTargets\n\nThree mechanisms control target execution order:\n\n| Mechanism | Effect | Use when |\n|---|---|---|\n| `BeforeTargets=\"X\"` | Runs this target before `X` | Injecting into an existing pipeline (e.g., run before `Build`) |\n| `AfterTargets=\"X\"` | Runs this target after `X` | Post-processing (e.g., copy output after `Publish`) |\n| `DependsOnTargets=\"A;B\"` | Ensures `A` and `B` run before this target | Declaring prerequisite targets within your own target graph |\n\n```xml\n\u003c!-- Run license check before compile -->\n\u003cTarget Name=\"CheckLicenseHeaders\"\n BeforeTargets=\"CoreCompile\">\n \u003cExec Command=\"dotnet tool run license-check -- --verify\" />\n\u003c/Target>\n\n\u003c!-- Copy native libs after publish -->\n\u003cTarget Name=\"CopyNativeLibs\"\n AfterTargets=\"Publish\">\n \u003cCopy SourceFiles=\"@(NativeLibrary)\"\n DestinationFolder=\"$(PublishDir)runtimes/%(NativeLibrary.RuntimeIdentifier)/native/\" />\n\u003c/Target>\n\n\u003c!-- Composite target with dependencies -->\n\u003cTarget Name=\"FullValidation\"\n DependsOnTargets=\"CheckLicenseHeaders;RunApiCompat\">\n \u003cMessage Importance=\"high\" Text=\"All validations passed.\" />\n\u003c/Target>\n```\n\n**Prefer `BeforeTargets`/`AfterTargets` over `DependsOnTargets`** for injecting into the standard build pipeline. `DependsOnTargets` is best for orchestrating your own custom target graph.\n\n### Extending Existing DependsOn Lists\n\nSDK targets expose `*DependsOn` properties for extension. Append your target name rather than replacing the list:\n\n```xml\n\u003cPropertyGroup>\n \u003cBuildDependsOn>$(BuildDependsOn);GenerateVersionInfo\u003c/BuildDependsOn>\n\u003c/PropertyGroup>\n\n\u003cTarget Name=\"GenerateVersionInfo\">\n \u003cWriteLinesToFile File=\"$(IntermediateOutputPath)Version.g.cs\"\n Lines=\"[assembly: System.Reflection.AssemblyInformationalVersion("$(InformationalVersion)")]\"\n Overwrite=\"true\" />\n \u003cItemGroup>\n \u003cCompile Include=\"$(IntermediateOutputPath)Version.g.cs\" />\n \u003c/ItemGroup>\n\u003c/Target>\n```\n\n\n## Incremental Build with Inputs/Outputs\n\nTargets with `Inputs` and `Outputs` only run when outputs are missing or older than inputs. This is critical for build performance.\n\n```xml\n\u003cTarget Name=\"GenerateEmbeddedResources\"\n BeforeTargets=\"CoreCompile\"\n Inputs=\"@(EmbeddedTemplate)\"\n Outputs=\"@(EmbeddedTemplate->'$(IntermediateOutputPath)%(Filename).g.cs')\">\n \u003cExec Command=\"dotnet tool run template-gen -- %(EmbeddedTemplate.Identity) -o $(IntermediateOutputPath)%(EmbeddedTemplate.Filename).g.cs\" />\n \u003cItemGroup>\n \u003cCompile Include=\"$(IntermediateOutputPath)%(EmbeddedTemplate.Filename).g.cs\" />\n \u003c/ItemGroup>\n\u003c/Target>\n```\n\n**How incrementality works:**\n\n1. MSBuild compares timestamps of `Inputs` items against `Outputs` items.\n2. If all outputs exist and are newer than all inputs, the target is skipped entirely.\n3. If any input is newer than any output, the full target runs.\n\n**Common incrementality failures:**\n\n- **Missing `Outputs`:** Target runs every build. Always pair `Inputs` with `Outputs`.\n- **Volatile outputs:** If another target writes to the output path mid-build, timestamps reset and trigger unnecessary rebuilds.\n- **Generator side effects:** Code generators that write unconditionally (even when content unchanged) break incrementality. Write to a temp file and copy only if content differs.\n- **File copy timestamps:** `Copy` task with `SkipUnchangedFiles=\"true\"` preserves timestamps; without it, every copy updates the timestamp.\n\n\n## Props vs Targets: Import Ordering\n\nMSBuild evaluates project files in a specific order. Understanding this is essential for correct customization.\n\n### Evaluation Order\n\n```\n1. Directory.Build.props (imported by SDK early)\n2. \u003cProject Sdk=\"...\"> (SDK props imported)\n3. Explicit \u003cImport> in project (your .props imports)\n4. Project body \u003cPropertyGroup>, (project-level properties)\n \u003cItemGroup>\n5. SDK targets imported (SDK targets)\n6. Directory.Build.targets (imported by SDK late)\n7. Explicit .targets imports (your .targets imports)\n```\n\n### Rules\n\n- **`.props` files** set default property values and define items. They run **before** the project body, so project-level properties can override them.\n- **`.targets` files** define targets and finalize item lists. They run **after** the project body, so they see all project-level settings.\n\n```xml\n\u003c!-- MyDefaults.props -- sets defaults, project can override -->\n\u003cProject>\n \u003cPropertyGroup>\n \u003cTreatWarningsAsErrors Condition=\"'$(TreatWarningsAsErrors)' == ''\">true\u003c/TreatWarningsAsErrors>\n \u003cNullable Condition=\"'$(Nullable)' == ''\">enable\u003c/Nullable>\n \u003c/PropertyGroup>\n\u003c/Project>\n```\n\n```xml\n\u003c!-- MyTargets.targets -- runs after project evaluation -->\n\u003cProject>\n \u003cTarget Name=\"ValidatePackageMetadata\"\n BeforeTargets=\"Pack\"\n Condition=\"'$(IsPackable)' == 'true'\">\n \u003cError Condition=\"'$(Description)' == ''\"\n Text=\"Description is required for packable projects.\" />\n \u003c/Target>\n\u003c/Project>\n```\n\n**Key rule:** Properties in `.props` files should use `Condition=\"'$(Prop)' == ''\"` to allow project-level overrides. Properties in `.targets` files are evaluated last and cannot be overridden by the project.\n\n\n## Items and Item Metadata\n\nItems are named collections of files or values. Each item can carry metadata (key-value pairs).\n\n### Item Operations\n\n```xml\n\u003cItemGroup>\n \u003c!-- Include: add items matching a glob -->\n \u003cContent Include=\"assets/**/*.png\" />\n\n \u003c!-- Exclude: remove items matching a pattern from the Include -->\n \u003cCompile Include=\"**/*.cs\" Exclude=\"**/*.generated.cs\" />\n\n \u003c!-- Update: modify metadata on existing items (does not add new items) -->\n \u003cContent Update=\"assets/logo.png\">\n \u003cCopyToOutputDirectory>PreserveNewest\u003c/CopyToOutputDirectory>\n \u003cPack>true\u003c/Pack>\n \u003cPackagePath>contentFiles/any/any/\u003c/PackagePath>\n \u003c/Content>\n\n \u003c!-- Remove: remove items matching a pattern from the item list -->\n \u003cCompile Remove=\"legacy/**/*.cs\" />\n\u003c/ItemGroup>\n```\n\n**SDK-style projects auto-include `*.cs` files.** Do not add a `\u003cCompile Include=\"**/*.cs\" />` -- it causes `NETSDK1022` duplicate items. Use `Remove` first, then `Include` for conditional compilation scenarios:\n\n```xml\n\u003c!-- TFM-conditional compilation -->\n\u003cItemGroup Condition=\"'$(TargetFramework)' == 'net8.0'\">\n \u003cCompile Remove=\"Polyfills/**/*.cs\" />\n\u003c/ItemGroup>\n```\n\n### Well-Known Metadata\n\nEvery item has built-in metadata accessible via `%(ItemName.MetadataName)`:\n\n| Metadata | Value | Example for `src/Models/Order.cs` |\n|---|---|---|\n| `%(FullPath)` | Absolute path | `/repo/src/Models/Order.cs` |\n| `%(RootDir)` | Root directory | `/` |\n| `%(Directory)` | Directory relative to root | `repo/src/Models/` |\n| `%(Filename)` | File name without extension | `Order` |\n| `%(Extension)` | File extension | `.cs` |\n| `%(RecursiveDir)` | Part matched by `**` in glob | `Models/` (if glob was `src/**/*.cs`) |\n| `%(Identity)` | Item spec as declared | `src/Models/Order.cs` |\n\n### Item Metadata and Batching\n\nCustom metadata enables per-item behavior through MSBuild batching:\n\n```xml\n\u003cItemGroup>\n \u003cDbMigration Include=\"migrations/*.sql\">\n \u003cTargetDb>main\u003c/TargetDb>\n \u003c/DbMigration>\n \u003cDbMigration Include=\"migrations/audit/*.sql\">\n \u003cTargetDb>audit\u003c/TargetDb>\n \u003c/DbMigration>\n\u003c/ItemGroup>\n\n\u003c!-- Batching: the task runs once per unique %(TargetDb) value -->\n\u003cTarget Name=\"RunMigrations\">\n \u003cExec Command=\"sqlcmd -d %(DbMigration.TargetDb) -i %(DbMigration.Identity)\"\n Condition=\"'@(DbMigration)' != ''\" />\n\u003c/Target>\n```\n\n`%(Metadata)` in a task attribute triggers batching. MSBuild groups items by the metadata value and invokes the task once per group.\n\n\n## Conditions\n\nConditions control whether properties, items, targets, and tasks are evaluated.\n\n### Property Conditions\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- Default: set only if not already defined -->\n \u003cLangVersion Condition=\"'$(LangVersion)' == ''\">latest\u003c/LangVersion>\n\n \u003c!-- TFM condition -->\n \u003cDefineConstants Condition=\"$(TargetFramework.StartsWith('net8'))\">$(DefineConstants);NET8_OR_GREATER\u003c/DefineConstants>\n\n \u003c!-- Configuration condition -->\n \u003cOptimize Condition=\"'$(Configuration)' == 'Release'\">true\u003c/Optimize>\n\n \u003c!-- OS condition -->\n \u003cRuntimeIdentifier Condition=\"$([MSBuild]::IsOSPlatform('Windows'))\">win-x64\u003c/RuntimeIdentifier>\n\u003c/PropertyGroup>\n```\n\n### Item and Target Conditions\n\n```xml\n\u003c!-- Conditional item inclusion -->\n\u003cItemGroup Condition=\"'$(TargetFramework)' == 'net8.0'\">\n \u003cPackageReference Include=\"Microsoft.Extensions.Hosting\" Version=\"8.0.0\" />\n\u003c/ItemGroup>\n\n\u003c!-- Conditional target execution -->\n\u003cTarget Name=\"SignAssembly\"\n AfterTargets=\"Build\"\n Condition=\"'$(Configuration)' == 'Release' AND '$(SignAssembly)' == 'true'\">\n \u003cExec Command=\"signtool sign /fd SHA256 $(TargetPath)\" />\n\u003c/Target>\n```\n\n### Condition Operators\n\n| Operator | Example |\n|---|---|\n| `==` / `!=` | `'$(Config)' == 'Release'` |\n| `AND` / `OR` | `'$(A)' == '1' AND '$(B)' != ''` |\n| `!` (negation) | `!Exists('$(OutDir)')` |\n| `Exists()` | `Exists('$(SolutionDir)global.json')` |\n| `HasTrailingSlash()` | `HasTrailingSlash('$(OutputPath)')` |\n\n**Always single-quote both sides of comparisons.** `'$(Prop)' == 'value'` is correct. Unquoted comparisons fail when the property is empty.\n\n\n## Property Functions\n\nMSBuild properties can call .NET static methods and MSBuild intrinsic functions inline.\n\n### .NET Static Method Calls\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- String manipulation -->\n \u003cNormalizedName>$([System.String]::Copy('$(PackageId)').ToLowerInvariant())\u003c/NormalizedName>\n\n \u003c!-- Path combination (prefer over string concatenation) -->\n \u003cToolPath>$([System.IO.Path]::Combine('$(MSBuildThisFileDirectory)', 'tools', 'analyzer.dll'))\u003c/ToolPath>\n\n \u003c!-- GUID generation -->\n \u003cBuildId>$([System.Guid]::NewGuid().ToString('N'))\u003c/BuildId>\n\n \u003c!-- Regex replacement -->\n \u003cCleanVersion>$([System.Text.RegularExpressions.Regex]::Replace('$(Version)', '-.*

dotnet-tooling Overview .NET project setup, build systems, performance, CLI apps, and developer tooling. This consolidated skill spans 34 topic areas. Load the appropriate companion file from based on the routing table below. Routing Table | Topic | Keywords | Description | Companion File | |-------|----------|-------------|----------------| | Project structure | solution, .slnx, CPM, analyzers | .slnx, Directory.Build.props, CPM, analyzers | references/project-structure.md | | Scaffold project | dotnet new, CPM, SourceLink, editorconfig | dotnet new with CPM, analyzers, editorconfig, SourceL…

, ''))\u003c/CleanVersion>\n\n \u003c!-- Environment variable -->\n \u003cCiServer>$([System.Environment]::GetEnvironmentVariable('CI'))\u003c/CiServer>\n\u003c/PropertyGroup>\n```\n\n### MSBuild Intrinsic Functions\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- OS detection -->\n \u003cIsWindows>$([MSBuild]::IsOSPlatform('Windows'))\u003c/IsWindows>\n \u003cIsLinux>$([MSBuild]::IsOSPlatform('Linux'))\u003c/IsLinux>\n \u003cIsMacOS>$([MSBuild]::IsOSPlatform('OSX'))\u003c/IsMacOS>\n\n \u003c!-- Arithmetic -->\n \u003cNextVersion>$([MSBuild]::Add($(PatchVersion), 1))\u003c/NextVersion>\n\n \u003c!-- Version comparison (MSBuild 17.0+) -->\n \u003cHasModernSdk>$([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '8.0.100'))\u003c/HasModernSdk>\n\n \u003c!-- Stable hash for deterministic output -->\n \u003cInputHash>$([MSBuild]::StableStringHash('$(InputFile)'))\u003c/InputHash>\n\n \u003c!-- Path resolution: find file by walking up directory tree -->\n \u003cSharedPropsPath>$([MSBuild]::GetPathOfFileAbove('SharedConfig.props', '$(MSBuildProjectDirectory)'))\u003c/SharedPropsPath>\n\n \u003c!-- Normalize path separators -->\n \u003cSafePath>$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '..', 'shared'))\u003c/SafePath>\n\u003c/PropertyGroup>\n```\n\n### Useful MSBuild Properties\n\n| Property | Value |\n|---|---|\n| `$(MSBuildProjectDirectory)` | Directory containing the current `.csproj` |\n| `$(MSBuildThisFileDirectory)` | Directory containing the current `.props`/`.targets` file |\n| `$(MSBuildProjectName)` | Project name without extension |\n| `$(IntermediateOutputPath)` | `obj/` output path |\n| `$(OutputPath)` | `bin/` output path |\n| `$(TargetFramework)` | Current TFM (e.g., `net10.0`) |\n| `$(TargetFrameworks)` | Multi-TFM list (e.g., `net8.0;net10.0`) |\n| `$(Configuration)` | `Debug` or `Release` |\n| `$(SolutionDir)` | Solution directory (only set when building from solution) |\n\n**Use `$(MSBuildThisFileDirectory)` in `.props`/`.targets` files**, not `$(MSBuildProjectDirectory)`. The former resolves to the file's own location, which is correct when the file is imported from a NuGet package or a different directory.\n\n\n## Directory.Build.props/targets Advanced Patterns\n\nBasic Directory.Build layout is covered in `references/project-structure.md`. This section covers advanced patterns for multi-repo and monorepo scenarios.\n\n### Import Chain with GetPathOfFileAbove\n\nIn monorepos with nested directories, each level can define its own `Directory.Build.props` that chains to the parent:\n\n```\nrepo/\n Directory.Build.props (repo-wide defaults)\n src/\n Directory.Build.props (src-specific overrides)\n MyApp/\n MyApp.csproj\n tests/\n Directory.Build.props (test-specific overrides)\n MyApp.Tests/\n MyApp.Tests.csproj\n```\n\n```xml\n\u003c!-- src/Directory.Build.props -->\n\u003cProject>\n \u003c!-- Chain to parent Directory.Build.props (with existence guard) -->\n \u003cPropertyGroup>\n \u003c_ParentBuildProps>$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)..'))\u003c/_ParentBuildProps>\n \u003c/PropertyGroup>\n \u003cImport Project=\"$(_ParentBuildProps)\" Condition=\"'$(_ParentBuildProps)' != ''\" />\n\n \u003cPropertyGroup>\n \u003c!-- Source-specific overrides -->\n \u003cGenerateDocumentationFile>true\u003c/GenerateDocumentationFile>\n \u003c/PropertyGroup>\n\u003c/Project>\n```\n\n```xml\n\u003c!-- tests/Directory.Build.props -->\n\u003cProject>\n \u003c!-- Chain to parent Directory.Build.props (with existence guard) -->\n \u003cPropertyGroup>\n \u003c_ParentBuildProps>$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)..'))\u003c/_ParentBuildProps>\n \u003c/PropertyGroup>\n \u003cImport Project=\"$(_ParentBuildProps)\" Condition=\"'$(_ParentBuildProps)' != ''\" />\n\n \u003cPropertyGroup>\n \u003cIsPackable>false\u003c/IsPackable>\n \u003cIsTestProject>true\u003c/IsTestProject>\n \u003c/PropertyGroup>\n\n \u003cItemGroup>\n \u003cPackageReference Include=\"Microsoft.NET.Test.Sdk\" />\n \u003cPackageReference Include=\"xunit.v3\" />\n \u003cPackageReference Include=\"xunit.runner.visualstudio\" />\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\n### Condition Guards\n\nPrevent property values from being overridden by child imports:\n\n```xml\n\u003c!-- repo/Directory.Build.props -->\n\u003cProject>\n \u003cPropertyGroup>\n \u003c!-- Defaults: can be overridden by child Directory.Build.props or project -->\n \u003cTreatWarningsAsErrors Condition=\"'$(TreatWarningsAsErrors)' == ''\">true\u003c/TreatWarningsAsErrors>\n \u003cNullable Condition=\"'$(Nullable)' == ''\">enable\u003c/Nullable>\n \u003cImplicitUsings Condition=\"'$(ImplicitUsings)' == ''\">enable\u003c/ImplicitUsings>\n \u003c/PropertyGroup>\n\u003c/Project>\n```\n\n**Rule:** Properties in `.props` files should use the `Condition=\"'$(Prop)' == ''\"` guard so that inner `.props` files and project-level properties can override them. Properties you want to enforce unconditionally belong in `Directory.Build.targets` (which evaluates last).\n\n### Preventing Double Imports\n\nWhen multiple `Directory.Build.props` files chain upward, a shared import could be evaluated twice. Use a sentinel property to guard against this:\n\n```xml\n\u003c!-- shared/Common.props -->\n\u003cProject>\n \u003c!-- Guard: only evaluate once -->\n \u003cPropertyGroup Condition=\"'$(_CommonPropsImported)' != 'true'\">\n \u003c_CommonPropsImported>true\u003c/_CommonPropsImported>\n \u003cAuthors>My Company\u003c/Authors>\n \u003cCompany>My Company\u003c/Company>\n \u003cCopyright>Copyright (c) My Company. All rights reserved.\u003c/Copyright>\n \u003c/PropertyGroup>\n\u003c/Project>\n```\n\nThe sentinel and content properties must be in the **same** `PropertyGroup` with the `!= 'true'` condition. Putting content in a separate block with `== 'true'` does not prevent re-evaluation -- it runs on every import because the sentinel is already set.\n\nA cleaner approach uses `Condition` on the `\u003cImport>` element:\n\n```xml\n\u003c!-- Only import if not already imported -->\n\u003cImport Project=\"$(SharedPropsPath)\"\n Condition=\"'$(_CommonPropsImported)' != 'true' AND Exists('$(SharedPropsPath)')\" />\n```\n\n### Enforcing Settings in Directory.Build.targets\n\nProperties set in `.targets` files cannot be overridden by project-level `PropertyGroup` elements because they evaluate after the project body:\n\n```xml\n\u003c!-- Directory.Build.targets -->\n\u003cProject>\n \u003c!-- Enforced: projects cannot override these -->\n \u003cPropertyGroup>\n \u003cEnforceCodeStyleInBuild>true\u003c/EnforceCodeStyleInBuild>\n \u003cAnalysisLevel>latest-recommended\u003c/AnalysisLevel>\n \u003c/PropertyGroup>\n\n \u003c!-- Conditional enforcement: only for src projects -->\n \u003cPropertyGroup Condition=\"'$(IsTestProject)' != 'true' AND '$(IsPackable)' != 'false'\">\n \u003cGenerateDocumentationFile>true\u003c/GenerateDocumentationFile>\n \u003c/PropertyGroup>\n\u003c/Project>\n```\n\n\n## Agent Gotchas\n\n1. **Unquoted condition comparisons.** Always quote both sides: `'$(Prop)' == 'value'`. Unquoted `$(Prop) == value` fails silently when the property is empty or contains spaces.\n\n2. **Using `$(MSBuildProjectDirectory)` in shared `.props`/`.targets` files.** This resolves to the importing project's directory, not the file's own directory. Use `$(MSBuildThisFileDirectory)` to reference paths relative to the `.props`/`.targets` file itself.\n\n3. **Setting properties in `.targets` and expecting project overrides.** Properties in `.targets` evaluate after the project body and override project-level values. If a property should be overridable, set it in `.props` with a `Condition=\"'$(Prop)' == ''\"` guard.\n\n4. **Adding `\u003cCompile Include=\"**/*.cs\" />` in SDK-style projects.** SDK-style projects auto-include all `*.cs` files. Explicit inclusion causes `NETSDK1022` duplicate items. Use `Remove` then `Include` for conditional scenarios.\n\n5. **Missing `Outputs` on targets with `Inputs`.** A target with `Inputs` but no `Outputs` runs every build. Always pair them for incremental behavior.\n\n6. **Using `$(SolutionDir)` in `.props`/`.targets` files.** This property is only set when building through a solution file. Command-line `dotnet build MyProject.csproj` leaves it empty. Use `$([MSBuild]::GetPathOfFileAbove('*.sln', '$(MSBuildProjectDirectory)'))` or pass paths relative to `$(MSBuildThisFileDirectory)`.\n\n7. **Putting items in `PropertyGroup` or properties in `ItemGroup`.** Items (using `Include=`) must be in `\u003cItemGroup>`. Properties (using element value) must be in `\u003cPropertyGroup>`. Mixing them produces silent evaluation failures.\n\n8. **Forgetting `Condition` guard on parent import chain.** `GetPathOfFileAbove` returns empty string when no file is found. The `\u003cImport>` must have `Condition=\"Exists('$(ResolvedPath)')\"` or the build fails with a file-not-found error.\n\n\n## References\n\n- [MSBuild Reference](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-reference)\n- [MSBuild Targets](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-targets)\n- [MSBuild Items](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-items)\n- [MSBuild Conditions](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions)\n- [MSBuild Property Functions](https://learn.microsoft.com/en-us/visualstudio/msbuild/property-functions)\n- [MSBuild Well-Known Item Metadata](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-well-known-item-metadata)\n- [Customize Your Build](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build)\n- [Directory.Build.props and Directory.Build.targets](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":20099,"content_sha256":"ea327752c5de9e53bbd7f512f8a5e1de1db7ebbd352bccebb52aa9f92c218ef2"},{"filename":"references/msbuild-tasks.md","content":"# MSBuild Tasks\n\nGuidance for authoring custom MSBuild tasks: implementing the `ITask` interface, extending `ToolTask` for CLI wrappers, using `IIncrementalTask` (MSBuild 17.8+) for incremental execution, defining inline tasks with `CodeTaskFactory`, registering tasks via `UsingTask`, declaring task parameters, debugging tasks, and packaging tasks as NuGet packages.\n\n**Version assumptions:** .NET 8.0+ SDK (MSBuild 17.8+). `IIncrementalTask` requires MSBuild 17.8+ (VS 2022 17.8+, .NET 8 SDK). All examples use SDK-style projects. All C# examples assume `using Microsoft.Build.Framework;` and `using Microsoft.Build.Utilities;` are in scope unless shown explicitly.\n\n## ITask Interface\n\nAll MSBuild tasks implement `Microsoft.Build.Framework.ITask`. The simplest approach is to inherit from `Microsoft.Build.Utilities.Task`, which provides default implementations for `BuildEngine` and `HostObject`.\n\n### Minimal Custom Task\n\n```csharp\nusing Microsoft.Build.Framework;\nusing Microsoft.Build.Utilities;\n\npublic class GenerateFileHash : Task\n{\n [Required]\n public string InputFile { get; set; } = string.Empty;\n\n [Output]\n public string Hash { get; set; } = string.Empty;\n\n public override bool Execute()\n {\n if (!File.Exists(InputFile))\n {\n Log.LogError(\"Input file not found: {0}\", InputFile);\n return false;\n }\n\n using var stream = File.OpenRead(InputFile);\n var bytes = System.Security.Cryptography.SHA256.HashData(stream);\n Hash = Convert.ToHexString(bytes).ToLowerInvariant();\n\n Log.LogMessage(MessageImportance.Normal,\n \"SHA-256 hash for {0}: {1}\", InputFile, Hash);\n return true;\n }\n}\n```\n\n### ITask Contract\n\n| Member | Purpose |\n|---|---|\n| `BuildEngine` | Provides logging, error reporting, and build context |\n| `HostObject` | Host-specific data (rarely used) |\n| `Execute()` | Runs the task. Return `true` for success, `false` for failure |\n\nThe `Task` base class exposes a `Log` property (`TaskLoggingHelper`) with convenience methods:\n\n| Method | When to use |\n|---|---|\n| `Log.LogMessage(importance, msg)` | Informational output (Normal, High, Low) |\n| `Log.LogWarning(msg)` | Non-fatal issues |\n| `Log.LogError(msg)` | Fatal errors (causes build failure) |\n| `Log.LogWarningFromException(ex)` | Warning from caught exception |\n| `Log.LogErrorFromException(ex)` | Error from caught exception |\n\n\nFor detailed code examples (ToolTask, IIncrementalTask, task parameters, inline tasks, UsingTask, debugging, NuGet packaging), see the Detailed Examples section below.\n\n## Agent Gotchas\n\n1. **Returning `false` without logging an error.** If `Execute()` returns `false` but `Log.LogError` was never called, MSBuild reports a generic \"task failed\" with no actionable message. Always log an error before returning `false`.\n\n2. **Using `Console.WriteLine` instead of `Log.LogMessage`.** Console output bypasses MSBuild's logging infrastructure and may not appear in build logs, binary logs, or IDE error lists. Always use `Log.LogMessage`, `Log.LogWarning`, or `Log.LogError`.\n\n3. **Referencing `IIncrementalTask` without version-gating.** This interface requires MSBuild 17.8+ (.NET 8 SDK). Tasks referencing it will fail to load on older MSBuild versions with a `TypeLoadException`. If supporting older SDKs, use target-level `Inputs`/`Outputs` instead. If the task must support both old and new MSBuild, ship separate task assemblies per MSBuild version range or use `#if` conditional compilation with a version constant.\n\n4. **Placing task DLLs in the NuGet `lib/` folder.** This adds the assembly as a compile reference to consuming projects, polluting their type namespace. Set `IncludeBuildOutput=false` and pack into `tools/` instead.\n\n5. **Forgetting `PrivateAssets=\"all\"` on MSBuild framework package references.** Without it, `Microsoft.Build.Framework` and `Microsoft.Build.Utilities.Core` become transitive dependencies of consuming projects, causing version conflicts.\n\n6. **Using `AssemblyFile` with a path relative to the project.** In NuGet packages, the `.targets` file is in a different location than the consuming project. Use `$(MSBuildThisFileDirectory)` to build paths relative to the `.targets` file itself.\n\n7. **Leaving `Debugger.Launch()` in release builds.** Shipping a task with unconditional `Debugger.Launch()` halts builds on CI/CD servers. Guard with `#if DEBUG` or remove before packaging.\n\n8. **Inline tasks with complex dependencies.** `CodeTaskFactory` compiles code at build time with limited assembly references. For tasks that need NuGet packages or complex type hierarchies, compile a standalone task assembly instead.\n\n\n## References\n\n- [MSBuild Task Writing](https://learn.microsoft.com/en-us/visualstudio/msbuild/task-writing)\n- [MSBuild Task Reference](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-task-reference)\n- [ToolTask Class](https://learn.microsoft.com/en-us/dotnet/api/microsoft.build.utilities.tooltask)\n- [MSBuild Inline Tasks](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-inline-tasks)\n- [UsingTask Element](https://learn.microsoft.com/en-us/visualstudio/msbuild/usingtask-element-msbuild)\n- [MSBuild Task Parameters](https://learn.microsoft.com/en-us/visualstudio/msbuild/task-writing#task-parameters)\n- [Creating a NuGet Package with MSBuild Tasks](https://learn.microsoft.com/en-us/nuget/create-packages/creating-a-package-msbuild)\n- [Debugging MSBuild Tasks](https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-debug-msbuild-custom-task)\n\n---\n\n# MSBuild Tasks -- Detailed Examples\n\nExtended code examples for ToolTask, IIncrementalTask, task parameters, inline tasks, UsingTask registration, debugging, and NuGet packaging.\n\n---\n\n## ToolTask Base Class\n\n`ToolTask` extends `Task` for wrapping external command-line tools. It handles process invocation, output capture, and exit code interpretation.\n\n```csharp\nusing Microsoft.Build.Framework;\nusing Microsoft.Build.Utilities;\n\npublic class RunLintTool : ToolTask\n{\n [Required]\n public string SourceDirectory { get; set; } = string.Empty;\n\n public string Severity { get; set; } = \"warning\";\n\n // Required: name of the executable\n protected override string ToolName => \"dotnet-lint\";\n\n // Required: full path or tool name (OS resolves via PATH)\n protected override string GenerateFullPathToTool()\n {\n // Return tool name; the OS resolves it via PATH at process start\n return ToolName;\n }\n\n // Required: build the command-line arguments\n protected override string GenerateCommandLineCommands()\n {\n var builder = new CommandLineBuilder();\n builder.AppendSwitch(\"--check\");\n builder.AppendSwitchIfNotNull(\"--severity \", Severity);\n builder.AppendFileNameIfNotNull(SourceDirectory);\n return builder.ToString();\n }\n\n // Optional: interpret non-zero exit codes\n protected override bool HandleTaskExecutionErrors()\n {\n Log.LogError(\"{0} found lint violations in {1}\",\n ToolName, SourceDirectory);\n return false;\n }\n}\n```\n\n### Key ToolTask Overrides\n\n| Override | Purpose |\n|---|---|\n| `ToolName` | Executable file name (e.g., `dotnet-lint`) |\n| `GenerateFullPathToTool()` | Full path to executable, or return `ToolName` to let the OS resolve via `PATH` |\n| `GenerateCommandLineCommands()` | Build argument string for the tool |\n| `GenerateResponseFileCommands()` | Arguments written to a response file (for long command lines) |\n| `HandleTaskExecutionErrors()` | Custom handling of non-zero exit codes |\n| `StandardOutputLoggingImportance` | Log level for stdout (default: `Low`) |\n| `StandardErrorLoggingImportance` | Log level for stderr (default: `Normal`) |\n\n### Response Files for Long Command Lines\n\nWhen the argument list is too long for the OS command line (common with many source files), use `GenerateResponseFileCommands()` to write arguments to a temporary response file:\n\n```csharp\nprotected override string GenerateResponseFileCommands()\n{\n var builder = new CommandLineBuilder();\n // These arguments go into a @response.rsp file\n foreach (var source in SourceFiles)\n {\n builder.AppendFileNameIfNotNull(source.ItemSpec);\n }\n return builder.ToString();\n}\n\nprotected override string GenerateCommandLineCommands()\n{\n // These arguments stay on the command line (before the @file ref)\n var builder = new CommandLineBuilder();\n builder.AppendSwitchIfNotNull(\"--config \", ConfigFile);\n return builder.ToString();\n}\n```\n\nMSBuild creates the response file, passes `@responsefile.rsp` to the tool, and cleans up afterward. The tool must support `@file` syntax (most .NET tools do).\n\n**When to use ToolTask vs Task:** Use `ToolTask` when wrapping an external CLI tool. Use `Task` (ITask) when the logic is pure .NET code with no external process.\n\n---\n\n## IIncrementalTask\n\n`Microsoft.Build.Framework.IIncrementalTask` (MSBuild 17.8+, VS 2022 17.8+, .NET 8 SDK) signals to the MSBuild engine that a task supports receiving pre-filtered inputs. When a target declares `Inputs`/`Outputs` and the engine determines which inputs have changed, it passes only the changed items to an `IIncrementalTask`-implementing task instead of the full item list.\n\n### Version Gate\n\n`IIncrementalTask` requires:\n- MSBuild 17.8+ (ships with VS 2022 17.8+)\n- .NET 8.0 SDK or later\n\nTasks targeting older MSBuild versions must not reference this interface. Use target-level `Inputs`/`Outputs` for incrementality on older versions. See `references/msbuild-authoring.md` for target-level incremental patterns.\n\n### How It Works\n\n1. The target declares `Inputs` and `Outputs` (required -- the engine uses these for change detection).\n2. MSBuild compares timestamps and determines which inputs are out of date.\n3. If the task implements `IIncrementalTask`, MSBuild passes only the changed items to the task's `ITaskItem[]` parameters instead of the full set.\n4. The task processes only those items -- no manual timestamp logic needed.\n\nThe `FailIfIncrementalBuildIsNotPossible` property controls fallback behavior:\n- `false` (default): If the engine cannot determine changed inputs (e.g., missing `Outputs`), it falls back to passing all inputs. The task runs in full-rebuild mode.\n- `true`: If the engine cannot provide incremental inputs, the task logs an error and fails. Use this when full rebuilds are unacceptably slow.\n\n### Implementation\n\n```csharp\nusing Microsoft.Build.Framework;\nusing Microsoft.Build.Utilities;\n\npublic class TransformTemplates : Task, IIncrementalTask\n{\n [Required]\n public ITaskItem[] Templates { get; set; } = [];\n\n [Output]\n public ITaskItem[] GeneratedFiles { get; set; } = [];\n\n // IIncrementalTask: if true, the task errors when the engine\n // cannot provide filtered inputs (falls back to full set if false)\n public bool FailIfIncrementalBuildIsNotPossible { get; set; }\n\n public override bool Execute()\n {\n // Templates contains ONLY changed items (filtered by engine)\n // when the target has Inputs/Outputs and incremental build is possible\n var outputs = new List\u003cITaskItem>();\n\n foreach (var template in Templates)\n {\n var inputPath = template.GetMetadata(\"FullPath\");\n var outputPath = Path.ChangeExtension(inputPath, \".g.cs\");\n\n var content = ProcessTemplate(File.ReadAllText(inputPath));\n File.WriteAllText(outputPath, content);\n\n Log.LogMessage(MessageImportance.Normal,\n \"Transformed: {0} -> {1}\", inputPath, outputPath);\n outputs.Add(new TaskItem(outputPath));\n }\n\n GeneratedFiles = outputs.ToArray();\n return true;\n }\n\n private static string ProcessTemplate(string input)\n {\n // Template transformation logic\n return $\"// Auto-generated\\n{input}\";\n }\n}\n```\n\n```xml\n\u003c!-- Target MUST declare Inputs/Outputs for engine-level change detection -->\n\u003cTarget Name=\"TransformAllTemplates\"\n BeforeTargets=\"CoreCompile\"\n Inputs=\"@(Template)\"\n Outputs=\"@(Template->'%(RootDir)%(Directory)%(Filename).g.cs')\">\n \u003cTransformTemplates Templates=\"@(Template)\">\n \u003cOutput TaskParameter=\"GeneratedFiles\" ItemName=\"Compile\" />\n \u003c/TransformTemplates>\n\u003c/Target>\n```\n\n**IIncrementalTask vs target-level Inputs/Outputs alone:** Without `IIncrementalTask`, target-level incrementality is all-or-nothing: if any input changed, the entire target re-runs with all items. With `IIncrementalTask`, the engine pre-filters the item list so the task receives only changed items, which is faster for targets that process large collections of files.\n\n---\n\n## Task Parameters\n\nTask parameters are public properties on the task class. MSBuild maps XML attributes to these properties automatically.\n\n### Parameter Attributes\n\n| Attribute | Effect |\n|---|---|\n| `[Required]` | Build fails if the parameter is not provided |\n| `[Output]` | Value is available to subsequent tasks/targets via `%(TaskName.PropertyName)` |\n| No attribute | Optional parameter with default value |\n\n### Parameter Types\n\n| .NET Type | MSBuild XML | Example |\n|---|---|---|\n| `string` | Scalar value | `InputFile=\"src/app.cs\"` |\n| `bool` | `true`/`false` | `Verbose=\"true\"` |\n| `int` | Numeric value | `MaxRetries=\"3\"` |\n| `string[]` | Semicolon-separated | `Assemblies=\"a.dll;b.dll\"` |\n| `ITaskItem` | Single item | `SourceFile=\"@(MainSource)\"` |\n| `ITaskItem[]` | Item collection | `SourceFiles=\"@(Compile)\"` |\n\n### ITaskItem Metadata Access\n\n`ITaskItem` carries rich metadata beyond the file path:\n\n```csharp\npublic class ProcessAssets : Task\n{\n [Required]\n public ITaskItem[] Assets { get; set; } = [];\n\n [Output]\n public ITaskItem[] ProcessedAssets { get; set; } = [];\n\n public override bool Execute()\n {\n var results = new List\u003cITaskItem>();\n\n foreach (var asset in Assets)\n {\n // ItemSpec = the Include value (relative path)\n var relativePath = asset.ItemSpec;\n\n // Built-in metadata\n var fullPath = asset.GetMetadata(\"FullPath\");\n var filename = asset.GetMetadata(\"Filename\");\n var extension = asset.GetMetadata(\"Extension\");\n\n // Custom metadata set in MSBuild XML\n var category = asset.GetMetadata(\"Category\");\n\n Log.LogMessage(MessageImportance.Normal,\n \"Processing {0} (category: {1})\", filename, category);\n\n var output = new TaskItem(\n Path.ChangeExtension(fullPath, \".processed\" + extension));\n // Copy all metadata from input to output\n asset.CopyMetadataTo(output);\n // Add new metadata\n output.SetMetadata(\"ProcessedAt\",\n DateTime.UtcNow.ToString(\"o\"));\n\n results.Add(output);\n }\n\n ProcessedAssets = results.ToArray();\n return true;\n }\n}\n```\n\n```xml\n\u003c!-- MSBuild usage -->\n\u003cItemGroup>\n \u003cGameAsset Include=\"textures/*.png\">\n \u003cCategory>texture\u003c/Category>\n \u003c/GameAsset>\n \u003cGameAsset Include=\"models/*.fbx\">\n \u003cCategory>model\u003c/Category>\n \u003c/GameAsset>\n\u003c/ItemGroup>\n\n\u003cTarget Name=\"ProcessGameAssets\" BeforeTargets=\"Build\">\n \u003cProcessAssets Assets=\"@(GameAsset)\">\n \u003cOutput TaskParameter=\"ProcessedAssets\" ItemName=\"ProcessedGameAsset\" />\n \u003c/ProcessAssets>\n\u003c/Target>\n```\n\n---\n\n## Inline Tasks (CodeTaskFactory)\n\nFor simple tasks that do not warrant a separate assembly, use `CodeTaskFactory` to define task logic inline in MSBuild XML. The code is compiled at build time.\n\n```xml\n\u003cUsingTask TaskName=\"GetTimestamp\"\n TaskFactory=\"CodeTaskFactory\"\n AssemblyFile=\"$(MSBuildToolsPath)\\Microsoft.Build.Tasks.Core.dll\">\n \u003cParameterGroup>\n \u003cFormat ParameterType=\"System.String\" Required=\"false\" />\n \u003cTimestamp ParameterType=\"System.String\" Output=\"true\" />\n \u003c/ParameterGroup>\n \u003cTask>\n \u003cCode Type=\"Fragment\" Language=\"cs\">\n \u003c![CDATA[\n var format = string.IsNullOrEmpty(Format) ? \"yyyyMMdd-HHmmss\" : Format;\n Timestamp = DateTime.UtcNow.ToString(format);\n ]]>\n \u003c/Code>\n \u003c/Task>\n\u003c/UsingTask>\n\n\u003c!-- Usage -->\n\u003cTarget Name=\"StampBuild\" BeforeTargets=\"CoreCompile\">\n \u003cGetTimestamp Format=\"yyyy.MMdd.HHmm\">\n \u003cOutput TaskParameter=\"Timestamp\" PropertyName=\"BuildTimestamp\" />\n \u003c/GetTimestamp>\n \u003cMessage Importance=\"high\" Text=\"Build timestamp: $(BuildTimestamp)\" />\n\u003c/Target>\n```\n\n### CodeTaskFactory Code Types\n\n| `Type` | Description |\n|---|---|\n| `Fragment` | Code runs inside the `Execute()` method body. Access parameters as local variables. |\n| `Method` | Code is a complete method body. Must include `return true;` or `return false;`. |\n| `Class` | Code is a full class. Must implement `ITask` or inherit from `Task`. |\n\n### Adding Assembly References\n\n```xml\n\u003cUsingTask TaskName=\"ValidateJson\"\n TaskFactory=\"CodeTaskFactory\"\n AssemblyFile=\"$(MSBuildToolsPath)\\Microsoft.Build.Tasks.Core.dll\">\n \u003cParameterGroup>\n \u003cJsonFile ParameterType=\"System.String\" Required=\"true\" />\n \u003cIsValid ParameterType=\"System.Boolean\" Output=\"true\" />\n \u003c/ParameterGroup>\n \u003cTask>\n \u003cReference Include=\"System.Text.Json\" />\n \u003cCode Type=\"Fragment\" Language=\"cs\">\n \u003c![CDATA[\n try\n {\n var content = System.IO.File.ReadAllText(JsonFile);\n System.Text.Json.JsonDocument.Parse(content);\n IsValid = true;\n }\n catch (System.Text.Json.JsonException)\n {\n IsValid = false;\n Log.LogWarning(\"Invalid JSON: {0}\", JsonFile);\n }\n ]]>\n \u003c/Code>\n \u003c/Task>\n\u003c/UsingTask>\n```\n\n**When to use inline tasks vs compiled tasks:** Inline tasks are best for simple, self-contained logic (timestamps, file checks, string manipulation). For complex logic, multiple dependencies, or reuse across projects, compile a task assembly and distribute via NuGet.\n\n---\n\n## UsingTask Registration\n\n`UsingTask` tells MSBuild where to find a custom task implementation. It must appear before any target that uses the task.\n\n### From a Compiled Assembly\n\n```xml\n\u003c!-- Register a task from a specific DLL -->\n\u003cUsingTask TaskName=\"MyCompany.Build.GenerateFileHash\"\n AssemblyFile=\"$(MSBuildThisFileDirectory)..\\tools\\MyCompany.Build.Tasks.dll\" />\n\n\u003c!-- Register using assembly name (GAC or resolved via AssemblySearchPaths) -->\n\u003cUsingTask TaskName=\"MyCompany.Build.GenerateFileHash\"\n AssemblyName=\"MyCompany.Build.Tasks, Version=1.0.0.0, Culture=neutral\" />\n```\n\n### Task Name Resolution\n\n| Attribute | Value | Effect |\n|---|---|---|\n| `TaskName` | `GenerateFileHash` | Short name; first match wins |\n| `TaskName` | `MyCompany.Build.GenerateFileHash` | Fully qualified; exact match |\n| `AssemblyFile` | Relative or absolute path | Load from file path |\n| `AssemblyName` | Strong name or simple name | Load by assembly identity |\n\n**Use `AssemblyFile` with `$(MSBuildThisFileDirectory)`** for tasks distributed via NuGet packages. The path resolves relative to the `.targets` file, not the consuming project.\n\n### Conditional Registration\n\n```xml\n\u003c!-- Only register task when the assembly exists (e.g., optional tooling) -->\n\u003cUsingTask TaskName=\"MyCompany.Build.CodeGen\"\n AssemblyFile=\"$(MSBuildThisFileDirectory)..\\tools\\MyCompany.Build.Tasks.dll\"\n Condition=\"Exists('$(MSBuildThisFileDirectory)..\\tools\\MyCompany.Build.Tasks.dll')\" />\n```\n\n---\n\n## Task Debugging\n\nDebugging custom MSBuild tasks requires attaching a debugger to the MSBuild process.\n\n### MSBUILDDEBUGONSTART\n\nSet the `MSBUILDDEBUGONSTART` environment variable before running the build:\n\n| Value | Behavior |\n|---|---|\n| `1` | MSBuild calls `Debugger.Launch()` at startup -- shows the JIT debugger attach dialog |\n| `2` | MSBuild waits for a debugger to attach (prints PID to console), then continues |\n\n```bash\n# Option 1: Launch debugger dialog (Windows)\nset MSBUILDDEBUGONSTART=1\ndotnet build\n\n# Option 2: Wait for debugger attach (cross-platform)\nexport MSBUILDDEBUGONSTART=2\ndotnet build\n# MSBuild prints: \"Waiting for debugger to attach (PID: 12345)...\"\n# Attach from VS or VS Code, then execution continues\n```\n\n### Debugging Workflow\n\n1. Set `MSBUILDDEBUGONSTART=2` in the terminal.\n2. Run `dotnet build` on the project that uses the custom task.\n3. MSBuild pauses and prints the process ID.\n4. Attach your debugger (Visual Studio: Debug > Attach to Process; VS Code: .NET Attach).\n5. Set breakpoints in the task's `Execute()` method.\n6. Continue execution -- the debugger hits your breakpoints.\n\n### Programmatic Debugger Launch\n\nFor development builds, add a conditional debugger launch inside the task:\n\n```csharp\npublic override bool Execute()\n{\n#if DEBUG\n if (!System.Diagnostics.Debugger.IsAttached)\n {\n System.Diagnostics.Debugger.Launch();\n }\n#endif\n // Task logic ...\n return true;\n}\n```\n\n**Remove or guard debugger launches before publishing.** Ship only Release builds of task assemblies. The `#if DEBUG` guard ensures no debugger prompts in production.\n\n---\n\n## Task NuGet Packaging\n\nCustom MSBuild tasks are typically distributed as NuGet packages. The package must place `.props`/`.targets` files and task assemblies in the correct folders.\n\n### Package Layout\n\n```\nMyCompany.Build.Tasks.nupkg\n build/\n MyCompany.Build.Tasks.props (optional: set defaults)\n MyCompany.Build.Tasks.targets (UsingTask + target definitions)\n buildTransitive/\n MyCompany.Build.Tasks.props (optional: set defaults)\n MyCompany.Build.Tasks.targets (UsingTask + target definitions)\n tools/\n net8.0/ (matches csproj TargetFramework)\n MyCompany.Build.Tasks.dll (task assembly)\n (other dependencies)\n```\n\n### build vs buildTransitive\n\n| Folder | Scope |\n|---|---|\n| `build/` | Targets/props apply to the **direct consumer** only |\n| `buildTransitive/` | Targets/props apply to the **direct consumer and all projects that transitively reference it** |\n\nUse `buildTransitive/` for tasks that must run in every project in the dependency graph (e.g., code analyzers, source generators). Use `build/` for tasks specific to the consuming project.\n\n### .targets File for NuGet Package\n\n```xml\n\u003c!-- build/MyCompany.Build.Tasks.targets -->\n\u003cProject>\n \u003c!-- TFM in path must match the csproj's TargetFramework -->\n \u003cUsingTask TaskName=\"MyCompany.Build.GenerateFileHash\"\n AssemblyFile=\"$(MSBuildThisFileDirectory)..\\tools\\net8.0\\MyCompany.Build.Tasks.dll\" />\n\n \u003cTarget Name=\"_MyCompanyHashOutputs\"\n AfterTargets=\"Build\"\n Condition=\"'$(GenerateOutputHashes)' == 'true'\">\n \u003cGenerateFileHash InputFile=\"$(TargetPath)\">\n \u003cOutput TaskParameter=\"Hash\" PropertyName=\"_OutputHash\" />\n \u003c/GenerateFileHash>\n \u003cMessage Importance=\"high\"\n Text=\"Output hash: $(_OutputHash)\" />\n \u003c/Target>\n\u003c/Project>\n```\n\n### .props File for NuGet Package\n\n```xml\n\u003c!-- build/MyCompany.Build.Tasks.props -->\n\u003cProject>\n \u003cPropertyGroup>\n \u003c!-- Default: consumers can override in their project file -->\n \u003cGenerateOutputHashes Condition=\"'$(GenerateOutputHashes)' == ''\">false\u003c/GenerateOutputHashes>\n \u003c/PropertyGroup>\n\u003c/Project>\n```\n\n### Project File for the Task Package\n\n```xml\n\u003c!-- MyCompany.Build.Tasks.csproj -->\n\u003cProject Sdk=\"Microsoft.NET.Sdk\">\n \u003cPropertyGroup>\n \u003cTargetFramework>net8.0\u003c/TargetFramework>\n \u003cIsPackable>true\u003c/IsPackable>\n \u003cPackageId>MyCompany.Build.Tasks\u003c/PackageId>\n \u003cPackageVersion>1.0.0\u003c/PackageVersion>\n \u003cDescription>Custom MSBuild tasks for MyCompany build pipeline\u003c/Description>\n\n \u003c!-- Do not add as a lib dependency -->\n \u003cIncludeBuildOutput>false\u003c/IncludeBuildOutput>\n\n \u003c!-- Suppress NU5100: task DLLs are in tools/, not lib/ -->\n \u003cNoWarn>$(NoWarn);NU5100\u003c/NoWarn>\n\n \u003c!-- Mark as a development dependency -->\n \u003cDevelopmentDependency>true\u003c/DevelopmentDependency>\n \u003c/PropertyGroup>\n\n \u003cItemGroup>\n \u003cPackageReference Include=\"Microsoft.Build.Framework\" Version=\"17.8.3\"\n PrivateAssets=\"all\" />\n \u003cPackageReference Include=\"Microsoft.Build.Utilities.Core\" Version=\"17.8.3\"\n PrivateAssets=\"all\" />\n \u003c/ItemGroup>\n\n \u003c!-- Pack task assembly into tools/ (uses TFM from project) -->\n \u003cItemGroup>\n \u003cNone Include=\"$(OutputPath)/**/*.dll\" Pack=\"true\"\n PackagePath=\"tools/$(TargetFramework)/\" />\n \u003c/ItemGroup>\n\n \u003c!-- Pack .props and .targets into build/ and buildTransitive/ -->\n \u003cItemGroup>\n \u003cNone Include=\"build/**\" Pack=\"true\" PackagePath=\"build/\" />\n \u003cNone Include=\"buildTransitive/**\" Pack=\"true\" PackagePath=\"buildTransitive/\" />\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\n**Key csproj settings:**\n- `IncludeBuildOutput=false` prevents the task DLL from appearing in the `lib/` folder (which would add it as a compile reference to consumers).\n- `DevelopmentDependency=true` marks the package as build-time only, so it does not flow to consumers' runtime dependencies.\n- `PrivateAssets=\"all\"` on MSBuild framework references prevents them from becoming transitive dependencies.\n\n---\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":25048,"content_sha256":"ddcba4a3daae722a380a8d2203bc3b2372ffb3a517b1afeb99420143960e9d0f"},{"filename":"references/multi-targeting.md","content":"# Multi-Targeting\n\nComprehensive guide for .NET multi-targeting strategies with a polyfill-first approach. This skill consumes the structured output from `references/version-detection.md` (TFM, C# version, preview flags) and provides actionable guidance on backporting language features, handling runtime gaps, and validating API compatibility across target frameworks.\n\n## Decision Matrix: Polyfill vs Conditional Compilation\n\nUse this matrix to select the correct strategy for each type of gap between your highest and lowest TFMs.\n\n| Gap Type | Strategy | When to Use | Example |\n|----------|----------|-------------|---------|\n| Language/syntax feature | Polyfill (PolySharp) | Compiler needs attribute/type stubs to emit newer syntax on older TFMs | `required` modifier, `init` properties, `SetsRequiredMembers` on net8.0 |\n| BCL API addition | Polyfill (SimonCropp/Polyfill) if available, else `#if` | A newer BCL type or method is missing on older TFMs | `System.Threading.Lock` on net8.0, `Index`/`Range` on netstandard2.0 |\n| Runtime behavior difference | Conditional compilation (`#if`) or adapter pattern | Behavior differs at runtime regardless of compilation | Runtime-async (net11.0 only), different GC modes, `SearchValues\u003cT>` runtime optimizations |\n| Platform API divergence | Conditional compilation with `[SupportedOSPlatform]` | API exists only on specific OS targets | Windows Registry APIs, Android-specific intents, iOS keychain |\n\n**Decision flow:**\n1. Can a compile-time polyfill satisfy the gap? Use PolySharp or SimonCropp/Polyfill.\n2. Is the gap a missing BCL API with no polyfill available? Use `#if` with TFM-specific code.\n3. Is the gap a runtime behavior difference? Use `#if` or the adapter pattern to isolate divergent code paths.\n4. Is the gap platform-specific? Use `#if` with `[SupportedOSPlatform]` attributes.\n\n\n## PolySharp (Compiler-Synthesized Polyfills)\n\nPolySharp is a source generator that synthesizes the attribute and type stubs the C# compiler needs to emit newer language features when targeting older TFMs. It operates entirely at compile time -- no runtime dependencies are added.\n\n### What PolySharp Provides\n\n- `required` modifier support (C# 11+)\n- `init` property accessors (C# 9+)\n- `SetsRequiredMembers` attribute\n- `CompilerFeatureRequired` attribute\n- `IsExternalInit` type\n- `CallerArgumentExpression` attribute\n- `StackTraceHidden` attribute\n- `UnscopedRef` attribute\n- `InterpolatedStringHandler` attributes\n- `ModuleInitializer` attribute\n- Index and Range support types\n\n### Setup\n\n```xml\n\u003cProject Sdk=\"Microsoft.NET.Sdk\">\n \u003cPropertyGroup>\n \u003cTargetFrameworks>net8.0;net10.0\u003c/TargetFrameworks>\n \u003c!-- Use the highest C# version across all TFMs -->\n \u003cLangVersion>14\u003c/LangVersion>\n \u003c/PropertyGroup>\n\n \u003cItemGroup>\n \u003c!-- PolySharp is a source generator; it adds no runtime dependency -->\n \u003cPackageReference Include=\"PolySharp\" Version=\"1.*\">\n \u003cPrivateAssets>all\u003c/PrivateAssets>\n \u003cIncludeAssets>runtime; build; native; contentfiles; analyzers\u003c/IncludeAssets>\n \u003c/PackageReference>\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\n### How It Works\n\nPolySharp detects which polyfill types are missing for the current TFM and generates source for only those types. On net10.0, where `required` is natively supported, the generator emits nothing -- zero overhead.\n\n```csharp\n// This compiles on net8.0 WITH PolySharp installed,\n// because PolySharp generates the required CompilerFeatureRequired\n// and IsExternalInit types that the compiler needs.\npublic class UserProfile\n{\n public required string DisplayName { get; init; }\n public required string Email { get; init; }\n public string? Bio { get; set; }\n}\n```\n\n### PolySharp Limitations\n\n- PolySharp provides **compiler stubs only**. It does not backport runtime behavior.\n- Features that require runtime support (e.g., runtime-async, `SearchValues\u003cT>` hardware acceleration) cannot be polyfilled.\n- If a feature needs both a compiler attribute AND a BCL API (e.g., collection expressions with `Span\u003cT>` overloads), you may need both PolySharp and SimonCropp/Polyfill.\n\n\n## SimonCropp/Polyfill (BCL API Polyfills)\n\nSimonCropp/Polyfill provides source-generated implementations of newer BCL APIs for older TFMs. Unlike PolySharp (which provides compiler attribute stubs), Polyfill provides actual method and type implementations.\n\n### What Polyfill Provides\n\nKey polyfilled APIs (non-exhaustive):\n\n- `System.Threading.Lock` (C# 13 / net9.0+)\n- `String.Contains(char)`, `String.Contains(string, StringComparison)`\n- `String.ReplaceLineEndings()`\n- `HashCode` struct\n- `SkipLocalsInit` attribute\n- `TaskCompletionSource` (non-generic)\n- `Stream.ReadExactly`, `Stream.ReadAtLeast`\n- `Memory\u003cT>` and `Span\u003cT>` extensions\n- `IReadOnlySet\u003cT>` interface\n- Various LINQ additions (`TryGetNonEnumeratedCount`, `DistinctBy`, `Chunk`, etc.)\n\n### Setup\n\n```xml\n\u003cProject Sdk=\"Microsoft.NET.Sdk\">\n \u003cPropertyGroup>\n \u003cTargetFrameworks>net8.0;net10.0\u003c/TargetFrameworks>\n \u003cLangVersion>14\u003c/LangVersion>\n \u003c/PropertyGroup>\n\n \u003cItemGroup>\n \u003c!-- Polyfill is a source generator; no runtime dependency -->\n \u003cPackageReference Include=\"Polyfill\" Version=\"7.*\">\n \u003cPrivateAssets>all\u003c/PrivateAssets>\n \u003cIncludeAssets>runtime; build; native; contentfiles; analyzers\u003c/IncludeAssets>\n \u003c/PackageReference>\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\n### Usage Example\n\n```csharp\n// System.Threading.Lock is a net9.0+ type.\n// With Polyfill installed, this compiles on net8.0.\npublic class ThrottledProcessor\n{\n private readonly Lock _lock = new();\n\n public void Process(string item)\n {\n lock (_lock)\n {\n // Lock provides better diagnostics than object-based locking\n Console.WriteLine($\"Processing: {item}\");\n }\n }\n}\n```\n\n### Combining PolySharp and Polyfill\n\nFor maximum compatibility, use both packages together. They are complementary and do not conflict:\n\n```xml\n\u003cItemGroup>\n \u003c!-- PolySharp: compiler attribute stubs (required, init, etc.) -->\n \u003cPackageReference Include=\"PolySharp\" Version=\"1.*\">\n \u003cPrivateAssets>all\u003c/PrivateAssets>\n \u003cIncludeAssets>runtime; build; native; contentfiles; analyzers\u003c/IncludeAssets>\n \u003c/PackageReference>\n\n \u003c!-- Polyfill: BCL API implementations (Lock, LINQ additions, etc.) -->\n \u003cPackageReference Include=\"Polyfill\" Version=\"7.*\">\n \u003cPrivateAssets>all\u003c/PrivateAssets>\n \u003cIncludeAssets>runtime; build; native; contentfiles; analyzers\u003c/IncludeAssets>\n \u003c/PackageReference>\n\u003c/ItemGroup>\n```\n\nWith both installed, you get full language feature support (PolySharp) **and** BCL API backporting (Polyfill) on older TFMs.\n\n\n## Conditional Compilation\n\nUse conditional compilation (`#if`) when the gap is a runtime behavior difference or a platform API that cannot be polyfilled at compile time.\n\n### TFM-Based Conditionals\n\nThe compiler defines preprocessor symbols for each TFM. Use `NET8_0_OR_GREATER`-style symbols (available since .NET 5) for version range checks:\n\n```csharp\npublic static class PerformanceHelper\n{\n#if NET10_0_OR_GREATER\n // net10.0+ has optimized SearchValues with hardware acceleration\n private static readonly SearchValues\u003cchar> s_vowels =\n SearchValues.Create(\"aeiouAEIOU\");\n\n public static int CountVowels(ReadOnlySpan\u003cchar> text)\n => text.Count(s_vowels);\n#else\n // Fallback for net8.0: manual loop\n public static int CountVowels(ReadOnlySpan\u003cchar> text)\n {\n int count = 0;\n foreach (char c in text)\n {\n if (\"aeiouAEIOU\".Contains(c))\n count++;\n }\n return count;\n }\n#endif\n}\n```\n\n### Available Preprocessor Symbols\n\n| Symbol | True When |\n|--------|-----------|\n| `NET8_0` | Exactly net8.0 |\n| `NET8_0_OR_GREATER` | net8.0 or any higher version |\n| `NET9_0_OR_GREATER` | net9.0 or any higher version |\n| `NET10_0_OR_GREATER` | net10.0 or any higher version |\n| `NET11_0_OR_GREATER` | net11.0 or any higher version |\n| `NETSTANDARD2_0` | Exactly netstandard2.0 |\n| `NETSTANDARD2_0_OR_GREATER` | netstandard2.0 or higher |\n\n### When #if Is Correct\n\n1. **Runtime behavior gap:** The API exists on both TFMs but behaves differently at runtime (e.g., `GC.Collect` modes, `HttpClient` connection pooling behavior).\n2. **No polyfill available:** The BCL API is not covered by SimonCropp/Polyfill and cannot be stubbed.\n3. **Performance-critical path:** You want to use a TFM-specific optimized API path (e.g., `SearchValues\u003cT>`, `FrozenDictionary\u003cK,V>`).\n4. **Platform API:** The API is available only on a specific OS platform target.\n\n### When #if Is Wrong\n\n- **Language syntax feature** (e.g., `required`, `init`): Use PolySharp instead.\n- **Missing BCL method** that has a polyfill (e.g., `System.Threading.Lock`): Use SimonCropp/Polyfill instead.\n- Wrapping entire files in `#if` blocks -- use TFM-specific source files instead (see below).\n\n\n## Multi-Targeting .csproj Patterns\n\n### Basic Multi-Targeting\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- Semicolon-delimited list of TFMs -->\n \u003cTargetFrameworks>net8.0;net10.0\u003c/TargetFrameworks>\n \u003c!-- Use the highest C# version to access all language features -->\n \u003cLangVersion>14\u003c/LangVersion>\n\u003c/PropertyGroup>\n```\n\n### Conditional Package References\n\nSome packages are only needed on specific TFMs:\n\n```xml\n\u003cItemGroup>\n \u003c!-- Polyfill packages: only needed on older TFMs, but safe to reference\n unconditionally because they emit nothing when features are native -->\n \u003cPackageReference Include=\"PolySharp\" Version=\"1.*\">\n \u003cPrivateAssets>all\u003c/PrivateAssets>\n \u003cIncludeAssets>runtime; build; native; contentfiles; analyzers\u003c/IncludeAssets>\n \u003c/PackageReference>\n\n \u003c!-- TFM-conditional package: only available/needed on specific TFMs -->\n \u003cPackageReference Include=\"System.Text.Json\" Version=\"9.*\"\n Condition=\"'$(TargetFramework)' == 'net8.0'\" />\n\u003c/ItemGroup>\n```\n\n### TFM-Specific Source Files\n\nFor large blocks of TFM-specific code, use dedicated source files instead of `#if` blocks:\n\n```xml\n\u003cItemGroup>\n \u003c!-- SDK-style projects auto-include all *.cs files. Remove TFM-specific\n directories first to avoid NETSDK1022 duplicate compile items. -->\n \u003cCompile Remove=\"Compatibility\\**\\*.cs\" />\n\n \u003c!-- Then conditionally include only the files for the current TFM -->\n \u003cCompile Include=\"Compatibility\\Net8\\**\\*.cs\"\n Condition=\"'$(TargetFramework)' == 'net8.0'\" />\n \u003cCompile Include=\"Compatibility\\Net10\\**\\*.cs\"\n Condition=\"$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net10.0'))\" />\n\u003c/ItemGroup>\n```\n\nDirectory structure:\n```\nMyLibrary/\n Compatibility/\n Net8/\n SearchValuesCompat.cs\n Net10/\n SearchValuesNative.cs\n Services/\n TextAnalyzer.cs # shared code, references interface\n MyLibrary.csproj\n```\n\n### Platform-Specific TFMs\n\nFor projects targeting platform-specific TFMs (MAUI, Uno):\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- Use version-agnostic platform globs where possible -->\n \u003cTargetFrameworks>net10.0;net10.0-android;net10.0-ios;net10.0-windows10.0.19041.0\u003c/TargetFrameworks>\n\u003c/PropertyGroup>\n\n\u003cItemGroup Condition=\"$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'\">\n \u003cPackageReference Include=\"Xamarin.AndroidX.Core\" Version=\"1.*\" />\n\u003c/ItemGroup>\n```\n\n### Shared Properties via Directory.Build.props\n\nFor multi-project solutions, centralize multi-targeting configuration:\n\n```xml\n\u003c!-- Directory.Build.props -->\n\u003cProject>\n \u003cPropertyGroup>\n \u003cLangVersion>14\u003c/LangVersion>\n \u003c/PropertyGroup>\n\n \u003cItemGroup>\n \u003cPackageReference Include=\"PolySharp\" Version=\"1.*\">\n \u003cPrivateAssets>all\u003c/PrivateAssets>\n \u003cIncludeAssets>runtime; build; native; contentfiles; analyzers\u003c/IncludeAssets>\n \u003c/PackageReference>\n \u003cPackageReference Include=\"Polyfill\" Version=\"7.*\">\n \u003cPrivateAssets>all\u003c/PrivateAssets>\n \u003cIncludeAssets>runtime; build; native; contentfiles; analyzers\u003c/IncludeAssets>\n \u003c/PackageReference>\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\nThis ensures all projects in the solution share the same polyfill setup. Individual projects set their own `\u003cTargetFrameworks>`.\n\n\n## API Compatibility Validation\n\nWhen publishing a NuGet package that targets multiple TFMs, validate that the public API surface is consistent and that you have not accidentally broken consumers.\n\n### EnablePackageValidation\n\nPackage validation runs automatically during `dotnet pack` and checks:\n- **Baseline validation:** Compares the current package against a previous version to detect breaking changes.\n- **Compatible framework validation:** Ensures APIs available on one TFM are available on all compatible TFMs.\n\n```xml\n\u003cPropertyGroup>\n \u003cTargetFrameworks>net8.0;net10.0\u003c/TargetFrameworks>\n \u003c!-- Enable package validation during pack -->\n \u003cEnablePackageValidation>true\u003c/EnablePackageValidation>\n \u003c!-- Compare against last published version for breaking change detection -->\n \u003cPackageValidationBaselineVersion>1.2.0\u003c/PackageValidationBaselineVersion>\n\u003c/PropertyGroup>\n```\n\n### API Compatibility Workflow\n\n**Step 1: Enable validation in .csproj**\n\n```xml\n\u003cPropertyGroup>\n \u003cEnablePackageValidation>true\u003c/EnablePackageValidation>\n\u003c/PropertyGroup>\n```\n\n**Step 2: Set baseline version (for existing packages)**\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- The last published stable version to compare against -->\n \u003cPackageValidationBaselineVersion>2.0.0\u003c/PackageValidationBaselineVersion>\n\u003c/PropertyGroup>\n```\n\n**Step 3: Pack and check**\n\n```bash\n# Pack triggers validation automatically\ndotnet pack --configuration Release\n\n# Success: no output about compatibility issues\n# Failure: error messages listing incompatible API changes\n```\n\n**Step 4: Interpret results**\n\n| Result | Meaning | Action |\n|--------|---------|--------|\n| Clean pack | All TFMs expose compatible API surfaces; no breaking changes from baseline | Ship |\n| `CP0001` | Missing type on a compatible TFM | Add the type to the TFM or use `#if` to exclude it from the public API |\n| `CP0002` | Missing member on a compatible TFM | Add the member or suppress if intentional |\n| `CP0003` | Breaking change from baseline version | Bump major version or revert the change |\n| `PKV004` | Compatible TFM has different API surface | Ensure conditional APIs are intentional |\n\n### Suppressing Known Differences\n\nFor intentional API differences between TFMs, use a suppression file. Package validation can generate one when suppression generation is enabled:\n\n```bash\n# Build with suppression-file generation enabled\ndotnet pack /p:ApiCompatGenerateSuppressionFile=true\n# Creates CompatibilitySuppressions.xml in the project directory\n```\n\nThe generated `CompatibilitySuppressions.xml` contains targeted suppressions:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003cSuppressions xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\n \u003cSuppression>\n \u003cDiagnosticId>CP0002\u003c/DiagnosticId>\n \u003cTarget>M:MyLib.PerformanceHelper.CountVowels(System.ReadOnlySpan{System.Char})\u003c/Target>\n \u003cLeft>lib/net8.0/MyLib.dll\u003c/Left>\n \u003cRight>lib/net10.0/MyLib.dll\u003c/Right>\n \u003c/Suppression>\n\u003c/Suppressions>\n```\n\nReference the suppression file in .csproj (automatic when file is at project root):\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- Explicit path if suppression file is not at project root -->\n \u003cApiCompatSuppressionFile>CompatibilitySuppressions.xml\u003c/ApiCompatSuppressionFile>\n\u003c/PropertyGroup>\n```\n\n**Prefer targeted suppression files over blanket `\u003cNoWarn>$(NoWarn);CP0002\u003c/NoWarn>`** -- blanket suppression hides real issues. Commit the suppression file to source control so reviewers can see intentional API differences.\n\n### ApiCompat Standalone Tool\n\nFor CI pipelines that validate without packing:\n\n```bash\n# Install as a global tool\ndotnet tool install -g Microsoft.DotNet.ApiCompat.Tool\n\n# Global tool invocation (after install -g)\napicompat --left-assembly bin/Release/net8.0/MyLib.dll \\\n --right-assembly bin/Release/net10.0/MyLib.dll\n\n# Or install as a local tool (preferred for CI reproducibility)\ndotnet new tool-manifest # if .config/dotnet-tools.json doesn't exist\ndotnet tool install Microsoft.DotNet.ApiCompat.Tool\n\n# Local tool invocation\ndotnet tool run apicompat --left-assembly bin/Release/net8.0/MyLib.dll \\\n --right-assembly bin/Release/net10.0/MyLib.dll\n```\n\n\n## Agent Gotchas\n\n1. **Do not use `#if` for language feature polyfills.** If the gap is a compiler attribute or syntax feature (e.g., `required`, `init`, `SetsRequiredMembers`), use PolySharp. `#if` blocks for language features create unnecessary code duplication and maintenance burden.\n\n2. **Do not omit `\u003cPrivateAssets>all\u003c/PrivateAssets>` on polyfill packages.** PolySharp and SimonCropp/Polyfill are source generators meant for compile time only. Without `PrivateAssets=all`, the polyfill types leak into your package's dependency graph and can conflict with consumers' own polyfills.\n\n3. **Do not hardcode TFM versions in conditional compilation.** Use `NET10_0_OR_GREATER`-style range symbols instead of `NET10_0` exact symbols. Exact symbols break when a new TFM is added (e.g., net11.0 would skip the net10.0-specific path). Range symbols automatically include future TFMs.\n\n4. **Do not set `\u003cLangVersion>` per TFM.** Set it once to the highest version needed across all TFMs (e.g., `\u003cLangVersion>14\u003c/LangVersion>`). PolySharp and Polyfill handle the backporting. Per-TFM LangVersion causes confusing syntax errors.\n\n5. **Do not skip `EnablePackageValidation` for multi-targeted NuGet packages.** Without it, you can accidentally expose different API surfaces on different TFMs, causing consumer build failures when they switch TFMs.\n\n6. **Do not use `$(TargetFramework)` string equality for range checks in MSBuild conditions.** Use `$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net10.0'))` for forward-compatible range checks. String equality (e.g., `== 'net10.0'`) misses net11.0 and higher.\n\n7. **Do not re-implement TFM detection.** This skill consumes the structured output from `references/version-detection.md`. Never parse `.csproj` files to determine TFMs -- use the detection skill's output (TFM, C# version, SDK version, warnings).\n\n8. **Do not assume polyfills cover runtime behavior.** PolySharp and Polyfill provide compile-time stubs and source-generated implementations. Features that require runtime changes (e.g., runtime-async, GC improvements, JIT optimizations) cannot be polyfilled -- use `#if` for these.\n\n9. **Do not use version-specific TFM globs for platform targets.** Use `net*-android` pattern matching (version-agnostic) instead of `net10.0-android` in documentation and tooling to avoid false negatives when users target different .NET versions.\n\n\n## Prerequisites\n\n- .NET 8.0+ SDK (multi-targeting requires the highest targeted SDK installed)\n- `PolySharp` NuGet package (for language feature polyfills)\n- `Polyfill` NuGet package by Simon Cropp (for BCL API polyfills)\n- `Microsoft.DotNet.ApiCompat.Tool` (optional, for standalone API compatibility checks)\n- Output from `references/version-detection.md` (TFM, C# version, SDK version)\n\n\n## References\n\n> **Last verified: 2026-02-12**\n\n- [PolySharp - Source Generator for Polyfill Attributes](https://github.com/Sergio0694/PolySharp)\n- [SimonCropp/Polyfill - Source-Only BCL Polyfills](https://github.com/SimonCropp/Polyfill)\n- [.NET Target Framework Monikers](https://learn.microsoft.com/en-us/dotnet/standard/frameworks)\n- [Multi-Targeting in .NET](https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/cross-platform-targeting)\n- [C# Preprocessor Directives](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives)\n- [Package Validation Overview](https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/overview)\n- [API Compatibility Overview](https://learn.microsoft.com/en-us/dotnet/fundamentals/apicompat/overview)\n- [MSBuild Target Framework Properties](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#targetframework)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":20024,"content_sha256":"a68f52012868c5c60c0b3342b20d1b2670eadb6888fe211ace60e542a6c156b3"},{"filename":"references/native-aot.md","content":"# Native AOT\n\nFull Native AOT compilation pipeline for .NET 8+ applications: `PublishAot` configuration, ILLink descriptor XML for type preservation, reflection-free coding patterns, P/Invoke considerations, binary size optimization, self-contained deployment with `runtime-deps` base images, and diagnostic analyzers (`EnableAotAnalyzer`/`EnableTrimAnalyzer`).\n\n**Version assumptions:** .NET 8.0+ baseline. Native AOT for ASP.NET Core Minimal APIs and console apps shipped in .NET 8. .NET 9 improved trimming warnings and library compat. .NET 10 enhanced request delegate generator and expanded Minimal API AOT support.\n\n## PublishAot Configuration\n\n### Enabling Native AOT\n\n```xml\n\u003c!-- App .csproj -->\n\u003cPropertyGroup>\n \u003cPublishAot>true\u003c/PublishAot>\n\u003c/PropertyGroup>\n```\n\n```bash\n# Publish as Native AOT\ndotnet publish -c Release -r linux-x64\n\n# Publish for specific targets\ndotnet publish -c Release -r win-x64\ndotnet publish -c Release -r osx-arm64\n```\n\n### MSBuild Properties: Apps vs Libraries\n\nApps and libraries use different MSBuild properties. Do not mix them.\n\n**For applications** (console apps, ASP.NET Core Minimal APIs):\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- Enable Native AOT compilation on publish -->\n \u003cPublishAot>true\u003c/PublishAot>\n\n \u003c!-- Enable analyzers during development (not just publish) -->\n \u003cEnableAotAnalyzer>true\u003c/EnableAotAnalyzer>\n \u003cEnableTrimAnalyzer>true\u003c/EnableTrimAnalyzer>\n\u003c/PropertyGroup>\n```\n\n**For libraries** (NuGet packages, shared class libraries):\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- Declare the library is AOT-compatible (auto-enables analyzers) -->\n \u003cIsAotCompatible>true\u003c/IsAotCompatible>\n \u003c!-- Declare the library is trim-safe (auto-enables trim analyzer) -->\n \u003cIsTrimmable>true\u003c/IsTrimmable>\n\u003c/PropertyGroup>\n```\n\n`IsAotCompatible` and `IsTrimmable` automatically enable the AOT and trim analyzers respectively. Do not also set `PublishAot` in library projects -- libraries are not published as standalone executables.\n\n\n## Diagnostic Analyzers\n\nEnable AOT and trim analyzers during development to catch issues before publishing:\n\n```xml\n\u003cPropertyGroup>\n \u003cEnableAotAnalyzer>true\u003c/EnableAotAnalyzer>\n \u003cEnableTrimAnalyzer>true\u003c/EnableTrimAnalyzer>\n\u003c/PropertyGroup>\n```\n\n### Analysis Without Publishing\n\nRun analysis during `dotnet build` without a full publish:\n\n```bash\n# Analyze AOT compatibility without publishing\ndotnet build /p:EnableAotAnalyzer=true /p:EnableTrimAnalyzer=true\n\n# See per-occurrence warnings (not grouped by assembly)\ndotnet build /p:EnableAotAnalyzer=true /p:EnableTrimAnalyzer=true /p:TrimmerSingleWarn=false\n```\n\nThis reports IL2xxx (trim) and IL3xxx (AOT) warnings without producing a native binary, enabling fast feedback during development.\n\n### Common Diagnostic Codes\n\n| Code | Category | Meaning |\n|------|----------|---------|\n| IL2026 | Trim | Member has `[RequiresUnreferencedCode]` -- may break after trimming |\n| IL2046 | Trim | Trim attribute mismatch between base/derived types |\n| IL2057-IL2072 | Trim | Various reflection usage that the trimmer cannot analyze |\n| IL3050 | AOT | Member has `[RequiresDynamicCode]` -- generates code at runtime |\n| IL3051 | AOT | `[RequiresDynamicCode]` attribute mismatch |\n\n\n## ILLink Descriptors for Type Preservation\n\nWhen code uses reflection that the trimmer cannot statically analyze, use ILLink descriptor XML to preserve types. **Do not use legacy RD.xml** -- it is a .NET Native/UWP format that is silently ignored by modern .NET AOT.\n\n### ILLink Descriptor XML\n\n```xml\n\u003c!-- ILLink.Descriptors.xml -->\n\u003clinker>\n \u003c!-- Preserve all public members of a type -->\n \u003cassembly fullname=\"MyApp\">\n \u003ctype fullname=\"MyApp.Models.LegacyConfig\" preserve=\"all\" />\n \u003ctype fullname=\"MyApp.Services.PluginLoader\">\n \u003cmethod name=\"LoadPlugin\" />\n \u003c/type>\n \u003c/assembly>\n\n \u003c!-- Preserve an entire external assembly -->\n \u003cassembly fullname=\"IncompatibleLibrary\" preserve=\"all\" />\n\u003c/linker>\n```\n\n```xml\n\u003c!-- Register in .csproj -->\n\u003cItemGroup>\n \u003cTrimmerRootDescriptor Include=\"ILLink.Descriptors.xml\" />\n\u003c/ItemGroup>\n```\n\n### `[DynamicDependency]` Attribute\n\nFor targeted preservation in code (preferred over ILLink XML for small, localized cases):\n\n```csharp\nusing System.Diagnostics.CodeAnalysis;\n\n// Preserve a specific method\n[DynamicDependency(nameof(LegacyConfig.Initialize), typeof(LegacyConfig))]\npublic void ConfigureApp() { /* ... */ }\n\n// Preserve all public members\n[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PluginBase))]\npublic void LoadPlugins() { /* ... */ }\n```\n\n### When to Use Which\n\n| Scenario | Approach |\n|----------|----------|\n| One or two methods/types | `[DynamicDependency]` attribute |\n| Entire assembly or many types | ILLink descriptor XML |\n| Third-party library not AOT-safe | ILLink descriptor XML or `\u003cTrimmerRootAssembly>` |\n| Your own code with analyzed reflection | Refactor to source generators (best long-term) |\n\n\n## Reflection-Free Patterns\n\nNative AOT works best with code that avoids runtime reflection entirely. Replace reflection patterns with compile-time alternatives.\n\n| Reflection Pattern | AOT-Safe Replacement |\n|-------------------|---------------------|\n| `Activator.CreateInstance\u003cT>()` | Factory method or explicit `new T()` |\n| `Type.GetProperties()` for mapping | Mapperly source generator or manual mapping |\n| `Assembly.GetTypes()` for DI scanning | Explicit `services.AddScoped\u003cT>()` |\n| `JsonSerializer.Deserialize\u003cT>(json)` | `JsonSerializer.Deserialize(json, Context.Default.T)` |\n| `MethodInfo.Invoke()` for dispatch | `switch` on type or interface dispatch |\n\nSee `references/aot-architecture.md` for comprehensive AOT-first design patterns.\n\n\n## P/Invoke Considerations\n\nP/Invoke (platform invoke) calls to native libraries generally work with Native AOT, but require attention:\n\n### Direct P/Invoke (Preferred)\n\n```csharp\n// Direct P/Invoke -- AOT-compatible, no runtime marshalling overhead\n[LibraryImport(\"libsqlite3\", EntryPoint = \"sqlite3_open\")]\ninternal static partial int Sqlite3Open(\n [MarshalAs(UnmanagedType.LPStr)] string filename,\n out nint db);\n```\n\nUse `[LibraryImport]` (.NET 7+) instead of `[DllImport]` -- it generates marshalling code at compile time via source generators, making it fully AOT-compatible.\n\n### DllImport vs LibraryImport\n\n| Attribute | AOT Compatibility | Marshalling |\n|-----------|------------------|-------------|\n| `[DllImport]` | Partial -- some marshalling requires runtime codegen | Runtime marshalling |\n| `[LibraryImport]` | Full -- compile-time source gen | Compile-time marshalling |\n\n```csharp\n// Migrate from DllImport to LibraryImport\n// Before:\n[DllImport(\"kernel32.dll\", SetLastError = true)]\nstatic extern bool CloseHandle(IntPtr hObject);\n\n// After:\n[LibraryImport(\"kernel32.dll\", SetLastError = true)]\n[return: MarshalAs(UnmanagedType.Bool)]\ninternal static partial bool CloseHandle(IntPtr hObject);\n```\n\n### Native Library Deployment\n\nWhen publishing as Native AOT, native libraries (`.so`, `.dylib`, `.dll`) must be alongside the binary:\n\n```xml\n\u003cItemGroup>\n \u003c!-- Include native library in publish output -->\n \u003cNativeLibrary Include=\"libs/libcustom.so\" />\n\u003c/ItemGroup>\n```\n\n\n## Size Optimization\n\n### Binary Size Reduction Options\n\n```xml\n\u003cPropertyGroup>\n \u003cPublishAot>true\u003c/PublishAot>\n\n \u003c!-- Strip debug symbols (significant size reduction) -->\n \u003cStripSymbols>true\u003c/StripSymbols>\n\n \u003c!-- Optimize for size over speed -->\n \u003cOptimizationPreference>Size\u003c/OptimizationPreference>\n\n \u003c!-- Enable invariant globalization (removes ICU data) -->\n \u003cInvariantGlobalization>true\u003c/InvariantGlobalization>\n\n \u003c!-- Remove stack trace strings (reduces size, harder debugging) -->\n \u003cStackTraceSupport>false\u003c/StackTraceSupport>\n\n \u003c!-- Remove EventSource/EventPipe (if not using diagnostics) -->\n \u003cEventSourceSupport>false\u003c/EventSourceSupport>\n\u003c/PropertyGroup>\n```\n\n### Typical Binary Sizes\n\n| Configuration | Console App | ASP.NET Minimal API |\n|--------------|-------------|---------------------|\n| Default AOT | ~10-15 MB | ~15-25 MB |\n| + StripSymbols | ~8-12 MB | ~12-20 MB |\n| + Size optimization | ~6-10 MB | ~10-18 MB |\n| + InvariantGlobalization | ~4-8 MB | ~8-15 MB |\n\n### Size Analysis\n\n```bash\n# Analyze what contributes to binary size\ndotnet publish -c Release -r linux-x64 /p:PublishAot=true\n\n# Use sizoscope (community tool) for detailed size analysis\n# https://github.com/AdrianEddy/sizoscope\n```\n\n\n## Self-Contained Deployment with runtime-deps\n\nNative AOT produces self-contained binaries that include the .NET runtime. Use the `runtime-deps` base image for minimal container size since the runtime is already embedded in the binary.\n\n```dockerfile\n# Build stage\nFROM mcr.microsoft.com/dotnet/sdk:10.0 AS build\nWORKDIR /src\nCOPY . .\nRUN dotnet publish -c Release -r linux-x64 -o /app/publish\n\n# Runtime stage -- use runtime-deps, not aspnet or runtime\nFROM mcr.microsoft.com/dotnet/runtime-deps:10.0-noble-chiseled AS runtime\nWORKDIR /app\nCOPY --from=build /app/publish .\nENTRYPOINT [\"./MyApp\"]\n```\n\nThe `runtime-deps` image contains only OS-level dependencies (libc, OpenSSL, etc.) -- no .NET runtime. This is the smallest possible base image for AOT-published apps (~30 MB). See [skill:dotnet-devops] for full container patterns.\n\n\n## ASP.NET Core Native AOT\n\n### Minimal API Support (.NET 8+)\n\nASP.NET Core Minimal APIs support Native AOT. MVC controllers are **not** AOT-compatible (they rely on reflection for model binding, filters, and routing).\n\n```csharp\nvar builder = WebApplication.CreateSlimBuilder(args);\n\n// Use source-generated JSON context\nbuilder.Services.ConfigureHttpJsonOptions(options =>\n{\n options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);\n});\n\nvar app = builder.Build();\n\napp.MapGet(\"/api/products/{id}\", (int id) =>\n Results.Ok(new Product(id, \"Widget\")));\n\napp.Run();\n\n[JsonSerializable(typeof(Product))]\ninternal partial class AppJsonContext : JsonSerializerContext { }\n\nrecord Product(int Id, string Name);\n```\n\n### CreateSlimBuilder vs CreateBuilder\n\n| Method | AOT Support | Includes |\n|--------|-------------|----------|\n| `WebApplication.CreateSlimBuilder()` | Full | Minimal services, no MVC, no Razor |\n| `WebApplication.CreateBuilder()` | Partial | Full feature set, some features need reflection |\n\nUse `CreateSlimBuilder` for Native AOT applications. It excludes features that require runtime code generation.\n\n### .NET 10 ASP.NET Core AOT Improvements\n\n.NET 10 brings improvements across the ASP.NET Core and runtime Native AOT stack. Target `net10.0` to benefit automatically.\n\n```xml\n\u003cPropertyGroup>\n \u003cTargetFramework>net10.0\u003c/TargetFramework>\n \u003cPublishAot>true\u003c/PublishAot>\n\u003c/PropertyGroup>\n```\n\n**Request Delegate Generator improvements:** The source generator that creates request delegates for Minimal API endpoints handles more parameter binding scenarios in .NET 10, including additional `TypedResults` return types and complex binding patterns. This reduces the need for manual workarounds that were required in .NET 8/9 when the generator could not produce AOT-safe code for certain endpoint signatures.\n\n**Reduced linker warning surface:** Many ASP.NET Core framework APIs that previously emitted trim/AOT warnings (IL2xxx/IL3xxx) have been annotated or refactored for AOT compatibility. Projects upgrading from .NET 9 to .NET 10 will see fewer false-positive linker warnings when publishing with `PublishAot`.\n\n**OpenAPI in the `webapiaot` template:** The `webapiaot` project template now includes OpenAPI document generation via `Microsoft.AspNetCore.OpenApi` by default, so AOT-published APIs get auto-generated API documentation without additional setup.\n\n**Runtime NativeAOT code generation:** The .NET 10 runtime improves AOT code generation for struct arguments, enhances loop inversion optimizations, and improves method devirtualization -- resulting in better throughput for AOT-published applications without code changes.\n\n**Blazor Server and SignalR:** Blazor Server and SignalR remain **not supported** with Native AOT in .NET 10. Blazor WebAssembly AOT (client-side compilation) is a separate concern covered by [skill:dotnet-ui]. For Blazor Server apps, continue using JIT deployment.\n\n**Compatibility snapshot (.NET 10):**\n\n| Feature | AOT Support |\n|---------|-------------|\n| gRPC | Fully supported |\n| Minimal APIs | Partially supported (most scenarios work) |\n| MVC | Not supported |\n| Blazor Server | Not supported |\n| SignalR | Not supported |\n| JWT Authentication | Fully supported |\n| CORS, HealthChecks, OutputCaching | Fully supported |\n| WebSockets, StaticFiles | Fully supported |\n\n\n## Agent Gotchas\n\n1. **Do not use `PublishAot` in library projects.** Libraries use `IsAotCompatible` (which auto-enables the AOT analyzer). `PublishAot` is for applications that produce standalone executables.\n2. **Do not use legacy RD.xml for type preservation.** RD.xml is a .NET Native/UWP format that is silently ignored by modern .NET AOT. Use ILLink descriptor XML files and `[DynamicDependency]` attributes instead.\n3. **Do not use `[DllImport]` in new AOT code.** Use `[LibraryImport]` (.NET 7+) which generates marshalling at compile time. `[DllImport]` may require runtime marshalling that is not available in AOT.\n4. **Do not use `WebApplication.CreateBuilder()` for AOT APIs.** Use `CreateSlimBuilder()` which excludes reflection-heavy features. `CreateBuilder()` includes MVC infrastructure that is not AOT-compatible.\n5. **Do not use `dotnet publish --no-actual-publish` for analysis.** That flag does not exist. Use `dotnet build /p:EnableAotAnalyzer=true /p:EnableTrimAnalyzer=true` to get diagnostic warnings without publishing.\n6. **Do not assume MVC controllers work with Native AOT.** MVC relies on reflection for model binding, action filters, and routing. Use Minimal APIs for AOT-published web applications.\n\n\n## References\n\n- [Native AOT deployment](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/)\n- [ASP.NET Core Native AOT](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/native-aot)\n- [ILLink descriptor format](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options#descriptor-format)\n- [LibraryImport source generation](https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke-source-generation)\n- [Optimize AOT deployments](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/optimizing)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":14389,"content_sha256":"83a49a55a8798d0ff637f852541561a534b9e7ddf048e296126a7ea84bfbdb33"},{"filename":"references/performance-patterns.md","content":"# Performance Patterns\n\nPerformance-oriented architecture patterns for .NET applications. Covers zero-allocation coding with Span\\\u003cT\\> and Memory\\\u003cT\\>, buffer pooling with ArrayPool\\\u003cT\\>, struct design for performance (readonly struct, ref struct, in parameters), sealed class devirtualization by the JIT, stack-based allocation with stackalloc, and string handling performance. Focuses on the **why** (performance rationale and measurement) rather than the **how** (language syntax).\n\n**Version assumptions:** .NET 8.0+ baseline. Span\\\u003cT\\> and Memory\\\u003cT\\> are available from .NET Core 2.1+ but this skill targets modern usage patterns on .NET 8+.\n\n## Span\\\u003cT\\> and Memory\\\u003cT\\> for Zero-Allocation Scenarios\n\n### Why Span\\\u003cT\\> Matters for Performance\n\n`Span\u003cT>` provides a safe, bounds-checked view over contiguous memory without allocating. It enables slicing arrays, strings, and stack memory without copying. For syntax details see [skill:dotnet-csharp]; this section focuses on performance rationale.\n\n### Zero-Allocation String Processing\n\n```csharp\n// BAD: Substring allocates a new string on each call\npublic static (string Key, string Value) ParseHeader_Allocating(string header)\n{\n var colonIndex = header.IndexOf(':');\n return (header.Substring(0, colonIndex), header.Substring(colonIndex + 1).Trim());\n}\n\n// GOOD: ReadOnlySpan\u003cchar> slicing avoids all allocations\npublic static (ReadOnlySpan\u003cchar> Key, ReadOnlySpan\u003cchar> Value) ParseHeader_ZeroAlloc(\n ReadOnlySpan\u003cchar> header)\n{\n var colonIndex = header.IndexOf(':');\n return (header[..colonIndex], header[(colonIndex + 1)..].Trim());\n}\n```\n\nPerformance impact: for high-throughput parsing (HTTP headers, log lines, CSV rows), Span-based parsing eliminates GC pressure entirely. Measure with `[MemoryDiagnoser]` in [skill:dotnet-testing] -- the `Allocated` column should read `0 B`.\n\n### Memory\\\u003cT\\> for Async and Storage Scenarios\n\n`Span\u003cT>` cannot be used in async methods or stored on the heap (it is a ref struct). Use `Memory\u003cT>` when you need to:\n\n- Pass buffers to async I/O methods\n- Store a slice reference in a field or collection\n- Return a memory region from a method for later consumption\n\n```csharp\npublic async Task\u003cint> ReadAndProcessAsync(Stream stream, Memory\u003cbyte> buffer)\n{\n var bytesRead = await stream.ReadAsync(buffer);\n var data = buffer[..bytesRead]; // Memory\u003cT> slicing -- no allocation\n return ProcessData(data.Span); // .Span for synchronous processing\n}\n\nprivate int ProcessData(ReadOnlySpan\u003cbyte> data)\n{\n var sum = 0;\n foreach (var b in data)\n sum += b;\n return sum;\n}\n```\n\n\n## ArrayPool\\\u003cT\\> for Buffer Pooling\n\n### Why Pool Buffers\n\nLarge array allocations (>= 85,000 bytes) go directly to the Large Object Heap (LOH), which is only collected in Gen 2 GC -- expensive and causes pauses. Even smaller arrays add GC pressure in hot paths. `ArrayPool\u003cT>` rents and returns buffers to avoid repeated allocations.\n\n### Usage Pattern\n\n```csharp\nusing System.Buffers;\n\npublic int ProcessLargeData(Stream source)\n{\n var buffer = ArrayPool\u003cbyte>.Shared.Rent(minimumLength: 81920);\n try\n {\n var bytesRead = source.Read(buffer, 0, buffer.Length);\n // IMPORTANT: Rent may return a larger buffer than requested.\n // Always use bytesRead or the requested length, never buffer.Length.\n return ProcessChunk(buffer.AsSpan(0, bytesRead));\n }\n finally\n {\n ArrayPool\u003cbyte>.Shared.Return(buffer, clearArray: true);\n // clearArray: true zeroes the buffer -- use when buffer held sensitive data\n }\n}\n```\n\n### Common Mistakes\n\n| Mistake | Impact | Fix |\n|---------|--------|-----|\n| Using `buffer.Length` instead of requested size | Processes uninitialized bytes beyond actual data | Track requested/actual size separately |\n| Forgetting to return the buffer | Pool exhaustion, falls back to allocation | Use try/finally or a `using` wrapper |\n| Returning a buffer twice | Corrupts pool state | Null out the reference after return |\n| Not clearing sensitive data | Security leak from pooled buffers | Pass `clearArray: true` to `Return` |\n\n\n## readonly struct, ref struct, and in Parameters\n\n### readonly struct -- Defensive Copy Elimination\n\nThe JIT must defensively copy non-readonly structs when accessed via `in`, `readonly` fields, or `readonly` methods to prevent mutation. Marking a struct `readonly` guarantees immutability, eliminating these copies:\n\n```csharp\n// GOOD: readonly eliminates defensive copies on every access\npublic readonly struct Point3D\n{\n public double X { get; }\n public double Y { get; }\n public double Z { get; }\n\n public Point3D(double x, double y, double z) => (X, Y, Z) = (x, y, z);\n\n // readonly struct: JIT knows this cannot mutate, no defensive copy needed\n public double DistanceTo(in Point3D other)\n {\n var dx = X - other.X;\n var dy = Y - other.Y;\n var dz = Z - other.Z;\n return Math.Sqrt(dx * dx + dy * dy + dz * dz);\n }\n}\n```\n\nWithout `readonly`, calling a method on a struct through an `in` parameter forces the JIT to copy the entire struct to protect against mutation. For large structs in tight loops, this eliminates significant overhead.\n\n### ref struct -- Stack-Only Types\n\n`ref struct` types are constrained to the stack. They cannot be boxed, stored in fields, or used in async methods. This enables safe wrapping of Span\\\u003cT\\>:\n\n```csharp\npublic ref struct SpanLineEnumerator\n{\n private ReadOnlySpan\u003cchar> _remaining;\n\n public SpanLineEnumerator(ReadOnlySpan\u003cchar> text) => _remaining = text;\n\n public ReadOnlySpan\u003cchar> Current { get; private set; }\n\n public bool MoveNext()\n {\n if (_remaining.IsEmpty)\n return false;\n\n var newlineIndex = _remaining.IndexOf('\\n');\n if (newlineIndex == -1)\n {\n Current = _remaining;\n _remaining = default;\n }\n else\n {\n Current = _remaining[..newlineIndex];\n _remaining = _remaining[(newlineIndex + 1)..];\n }\n return true;\n }\n}\n```\n\n### in Parameters -- Pass-by-Reference Without Mutation\n\nUse `in` for large readonly structs passed to methods. The `in` modifier passes by reference (avoids copying) and prevents mutation:\n\n```csharp\n// in parameter: pass by reference, no copy, no mutation allowed\npublic static double CalculateDistance(in Point3D a, in Point3D b)\n => a.DistanceTo(in b);\n```\n\n**When to use `in`:**\n\n| Struct Size | Recommendation |\n|-------------|---------------|\n| \u003c= 16 bytes | Pass by value (register-friendly, no indirection overhead) |\n| > 16 bytes | Use `in` to avoid copy overhead |\n| Any size, readonly struct | `in` is safe (no defensive copies) |\n| Any size, non-readonly struct | Avoid `in` (defensive copies negate the benefit) |\n\n\n## Sealed Class Performance Rationale\n\n### JIT Devirtualization\n\nWhen a class is `sealed`, the JIT can replace virtual method calls with direct calls (devirtualization) because no subclass override is possible. This enables further inlining:\n\n```csharp\n// Without sealed: virtual dispatch through vtable\npublic class OpenService : IProcessor\n{\n public virtual int Process(int x) => x * 2;\n}\n\n// With sealed: JIT devirtualizes + inlines Process call\npublic sealed class SealedService : IProcessor\n{\n public int Process(int x) => x * 2;\n}\n\npublic interface IProcessor { int Process(int x); }\n```\n\nVerify devirtualization with `[DisassemblyDiagnoser]` in [skill:dotnet-testing]. See [skill:dotnet-csharp] for the project convention of defaulting to sealed classes.\n\n### Performance Impact\n\nDevirtualization + inlining eliminates:\n1. **vtable lookup** -- indirect memory access to find the method pointer\n2. **Call overhead** -- the actual indirect call instruction\n3. **Inlining barrier** -- virtual calls cannot be inlined; sealed methods can\n\nIn tight loops and hot paths, the cumulative effect is measurable. For framework/library types that are not designed for extension, always prefer `sealed`.\n\n\n## stackalloc for Small Stack-Based Allocations\n\n### When to Use stackalloc\n\n`stackalloc` allocates memory on the stack, avoiding GC entirely. Use for small, fixed-size buffers in hot paths:\n\n```csharp\npublic static string FormatGuid(Guid guid)\n{\n // 68 bytes on the stack -- well within safe limits\n Span\u003cchar> buffer = stackalloc char[68];\n guid.TryFormat(buffer, out var charsWritten, \"D\");\n return new string(buffer[..charsWritten]);\n}\n```\n\n### Safety Guidelines\n\n| Guideline | Rationale |\n|-----------|-----------|\n| Keep allocations small (\u003c 1 KB typical, \u003c 4 KB absolute maximum) | Stack space is limited (~1 MB default on Windows); overflow crashes the process |\n| Use constant or bounded sizes only | Runtime-variable sizes risk stack overflow from malicious/unexpected input |\n| Prefer `Span\u003cT>` assignment over raw pointer | Span provides bounds checking; raw pointers do not |\n| Fall back to ArrayPool for large/variable sizes | Gracefully handle cases that exceed stack budget |\n\n### Hybrid Pattern: stackalloc with ArrayPool Fallback\n\n```csharp\npublic static string ProcessData(ReadOnlySpan\u003cbyte> input)\n{\n const int stackThreshold = 256;\n char[]? rented = null;\n\n Span\u003cchar> buffer = input.Length \u003c= stackThreshold\n ? stackalloc char[stackThreshold]\n : (rented = ArrayPool\u003cchar>.Shared.Rent(input.Length));\n\n try\n {\n var written = Encoding.UTF8.GetChars(input, buffer);\n return new string(buffer[..written]);\n }\n finally\n {\n if (rented is not null)\n ArrayPool\u003cchar>.Shared.Return(rented);\n }\n}\n```\n\nThis pattern is used throughout the .NET runtime libraries and is the recommended approach for methods that handle both small and large inputs.\n\n\n## String Interning and StringComparison Performance\n\n### String Comparison Performance\n\nOrdinal comparisons are significantly faster than culture-aware comparisons because they avoid Unicode normalization:\n\n```csharp\n// FAST: ordinal comparison (byte-by-byte)\nbool isMatch = str.Equals(\"expected\", StringComparison.Ordinal);\nbool containsKey = dict.ContainsKey(key); // Dictionary\u003cstring, T> uses ordinal by default\n\n// FAST: case-insensitive ordinal (no culture overhead)\nbool isMatchIgnoreCase = str.Equals(\"expected\", StringComparison.OrdinalIgnoreCase);\n\n// SLOW: culture-aware comparison (Unicode normalization, linguistic rules)\nbool isMatchCulture = str.Equals(\"expected\", StringComparison.CurrentCulture);\n```\n\n**Default guidance:** Use `StringComparison.Ordinal` or `StringComparison.OrdinalIgnoreCase` for internal identifiers, dictionary keys, file paths, and protocol strings. Reserve culture-aware comparison for user-visible text sorting and display.\n\n### String Interning\n\nThe CLR interns compile-time string literals automatically. `string.Intern()` can reduce memory for runtime strings that repeat frequently:\n\n```csharp\n// Intern frequently-repeated runtime strings to share a single instance\nvar normalized = string.Intern(headerName.ToLowerInvariant());\n```\n\n**Caution:** Interned strings are never garbage collected. Only intern strings from a bounded, known set (HTTP headers, XML element names). Never intern user input or unbounded data.\n\n### Efficient String Building\n\n| Scenario | Recommended Approach | Why |\n|----------|---------------------|-----|\n| 2-3 concatenations | String interpolation `$\"{a}{b}\"` | Compiler optimizes to `string.Concat` |\n| Loop concatenation | `StringBuilder` | Avoids quadratic allocation |\n| Known fixed parts | `string.Create` | Single allocation, Span-based writing |\n| High-throughput formatting | `Span\u003cchar>` + `TryFormat` | Zero-allocation formatting |\n\n```csharp\n// string.Create for single-allocation building\npublic static string FormatId(int category, int item)\n{\n return string.Create(11, (category, item), static (span, state) =>\n {\n state.category.TryFormat(span, out var catWritten);\n span[catWritten] = '-';\n state.item.TryFormat(span[(catWritten + 1)..], out _);\n });\n}\n```\n\n\n## Performance Measurement Checklist\n\nBefore applying any optimization pattern, measure first. Premature optimization without data leads to complex code with no measurable benefit.\n\n1. **Identify the hot path** -- use [skill:dotnet-testing] to establish a baseline\n2. **Measure allocations** -- enable `[MemoryDiagnoser]` and check the `Allocated` column\n3. **Apply one pattern at a time** -- change one thing, re-measure, compare to baseline\n4. **Check AOT impact** -- if targeting Native AOT (`references/native-aot.md`), verify patterns are trim-safe\n5. **Verify with production-like data** -- synthetic benchmarks can miss real-world allocation patterns\n6. **Document the tradeoff** -- every optimization trades readability or flexibility for speed; record the measured gain\n\n\n## Agent Gotchas\n\n1. **Measure before optimizing** -- never apply Span/ArrayPool/stackalloc without a benchmark showing the allocation or latency problem. Premature optimization produces unreadable code for no measurable gain.\n2. **Do not use stackalloc with variable sizes from untrusted input** -- stack overflow crashes the process with no exception handler. Always validate bounds or use the hybrid stackalloc/ArrayPool pattern.\n3. **Always mark value types `readonly struct` when they are immutable** -- without `readonly`, the JIT generates defensive copies on every `in` parameter access and `readonly` field access, silently negating the performance benefit of using structs.\n4. **Return rented ArrayPool buffers in finally blocks** -- forgetting to return starves the pool and causes fallback allocations that negate the benefit.\n5. **Use `StringComparison.Ordinal` for internal comparisons** -- omitting the comparison parameter defaults to culture-aware comparison, which is slower and produces surprising results for technical strings (file paths, identifiers).\n6. **Sealed classes help performance only when the JIT can see the concrete type** -- if the object is accessed through an interface variable in a non-devirtualizable call site, sealing provides no benefit. Verify with `[DisassemblyDiagnoser]`.\n7. **Do not re-teach language syntax** -- reference [skill:dotnet-csharp] for Span/Memory syntax details. This skill focuses on when and why to use these patterns for performance.\n\n\n## Knowledge Sources\n\nPerformance patterns in this skill are grounded in guidance from:\n\n- **Stephen Toub** -- .NET Performance blog series ([devblogs.microsoft.com/dotnet/author/toub](https://devblogs.microsoft.com/dotnet/author/toub/)). Authoritative source on Span\\\u003cT\\>, ValueTask, ArrayPool, async internals, and runtime performance characteristics.\n- **Stephen Cleary** -- Async best practices and concurrent collections guidance. Author of *Concurrency in C# Cookbook*.\n- **Nick Chapsas** -- Modern .NET performance patterns and benchmarking methodology.\n\n> These sources inform the patterns and rationale presented above. This skill does not claim to represent or speak for any individual.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":15024,"content_sha256":"5987a75842dc203c6f51b12a434796d330dd4512269da3ed809fecc2fcea7c2a"},{"filename":"references/profiling.md","content":"# Profiling\n\nDiagnostic tool guidance for investigating .NET performance problems. Covers real-time metric monitoring with dotnet-counters, event tracing and flame graph generation with dotnet-trace, and memory dump capture and analysis with dotnet-dump. Focuses on interpreting profiling data (reading flame graphs, analyzing heap dumps, correlating GC metrics) rather than just invoking tools.\n\n**Version assumptions:** .NET SDK 8.0+ baseline. All three diagnostic tools (dotnet-counters, dotnet-trace, dotnet-dump) ship with the .NET SDK -- no separate installation required.\n\n## dotnet-counters -- Real-Time Metric Monitoring\n\n### Overview\n\n`dotnet-counters` provides real-time monitoring of .NET runtime metrics without modifying application code. Use it as a first-pass triage tool to identify whether a performance problem is CPU-bound, memory-bound, or I/O-bound before reaching for heavier instrumentation.\n\n### Monitoring Running Processes\n\n```bash\n# List running .NET processes\ndotnet-counters ps\n\n# Monitor default runtime counters for a process\ndotnet-counters monitor --process-id \u003cPID>\n\n# Monitor with a specific refresh interval (seconds)\ndotnet-counters monitor --process-id \u003cPID> --refresh-interval 2\n```\n\n### Key Built-In Counter Providers\n\n| Provider | Counters | What It Tells You |\n|----------|----------|-------------------|\n| `System.Runtime` | CPU usage, GC heap size, Gen 0/1/2 collections, threadpool queue length, exception count | Overall runtime health |\n| `Microsoft.AspNetCore.Hosting` | Request rate, request duration, active requests | HTTP request throughput and latency |\n| `Microsoft.AspNetCore.Http.Connections` | Connection duration, current connections | WebSocket/SignalR connection load |\n| `System.Net.Http` | Requests started/failed, active requests, connection pool size | Outbound HTTP client behavior |\n| `System.Net.Sockets` | Bytes sent/received, datagrams, connections | Network I/O volume |\n\n### Monitoring Specific Providers\n\n```bash\n# Monitor runtime and ASP.NET counters together\ndotnet-counters monitor --process-id \u003cPID> \\\n --counters System.Runtime,Microsoft.AspNetCore.Hosting\n\n# Monitor only GC-related counters\ndotnet-counters monitor --process-id \u003cPID> \\\n --counters System.Runtime[gc-heap-size,gen-0-gc-count,gen-1-gc-count,gen-2-gc-count]\n```\n\n### Custom EventCounters\n\nApplications can publish custom counters for domain-specific metrics:\n\n```csharp\nusing System.Diagnostics.Tracing;\n\n[EventSource(Name = \"MyApp.Orders\")]\npublic sealed class OrderMetrics : EventSource\n{\n public static readonly OrderMetrics Instance = new();\n\n private EventCounter? _orderProcessingTime;\n private IncrementingEventCounter? _ordersProcessed;\n\n private OrderMetrics()\n {\n _orderProcessingTime = new EventCounter(\"order-processing-time\", this)\n {\n DisplayName = \"Order Processing Time (ms)\",\n DisplayUnits = \"ms\"\n };\n _ordersProcessed = new IncrementingEventCounter(\"orders-processed\", this)\n {\n DisplayName = \"Orders Processed\",\n DisplayRateTimeScale = TimeSpan.FromSeconds(1)\n };\n }\n\n public void RecordProcessingTime(double milliseconds)\n => _orderProcessingTime?.WriteMetric(milliseconds);\n\n public void RecordOrderProcessed()\n => _ordersProcessed?.Increment();\n\n protected override void Dispose(bool disposing)\n {\n _orderProcessingTime?.Dispose();\n _ordersProcessed?.Dispose();\n base.Dispose(disposing);\n }\n}\n```\n\nMonitor custom counters:\n\n```bash\ndotnet-counters monitor --process-id \u003cPID> --counters MyApp.Orders\n```\n\n### Interpreting Counter Data\n\nUse counter values to direct further investigation. See [skill:dotnet-devops] for correlating these runtime metrics with OpenTelemetry traces:\n\n| Symptom | Counter Evidence | Next Step |\n|---------|------------------|-----------|\n| High CPU usage | `cpu-usage` > 80%, `threadpool-queue-length` low | CPU profiling with dotnet-trace |\n| Memory growth | `gc-heap-size` increasing, frequent Gen 2 GC | Memory dump with dotnet-dump |\n| Thread starvation | `threadpool-queue-length` growing, `threadpool-thread-count` at max | Check for sync-over-async or blocking calls |\n| Request latency | `request-duration` high, `active-requests` normal | Trace individual requests with dotnet-trace |\n| GC pauses | High `gen-2-gc-count`, `time-in-gc` > 10% | Allocation profiling with dotnet-trace gc-collect |\n\n### Exporting Counter Data\n\n```bash\n# Export to CSV for analysis\ndotnet-counters collect --process-id \u003cPID> \\\n --format csv \\\n --output counters.csv \\\n --counters System.Runtime\n\n# Export to JSON for programmatic consumption\ndotnet-counters collect --process-id \u003cPID> \\\n --format json \\\n --output counters.json\n```\n\n\n## dotnet-trace -- Event Tracing and Flame Graphs\n\n### Overview\n\n`dotnet-trace` captures detailed event traces from a running .NET process. Traces can be analyzed as flame graphs to identify CPU hot paths, or configured for allocation tracking to find GC pressure sources.\n\n### CPU Sampling\n\nCPU sampling records stack frames at a fixed interval to build a statistical profile of where the application spends time:\n\n```bash\n# Collect a CPU sampling trace (default profile)\ndotnet-trace collect --process-id \u003cPID> --duration 00:00:30\n\n# Collect with the cpu-sampling profile (explicit)\ndotnet-trace collect --process-id \u003cPID> \\\n --profile cpu-sampling \\\n --output cpu-trace.nettrace\n```\n\n### CPU Sampling vs Instrumentation\n\n| Approach | Overhead | Best For | Tool |\n|----------|----------|----------|------|\n| CPU sampling | Low (~2-5%) | Finding CPU hot paths in production | dotnet-trace `--profile cpu-sampling` |\n| Instrumentation | High (10-50%+) | Exact call counts, method entry/exit timing | Rider/VS profiler, PerfView |\n\nCPU sampling is safe for production use due to low overhead. Use it as the default approach. Reserve instrumentation for development environments where exact call counts matter.\n\n### Flame Graph Generation\n\nTrace files (`.nettrace`) must be converted to a flame graph format for visual analysis:\n\n**Using Speedscope (browser-based, recommended):**\n\n```bash\n# Convert to Speedscope format\ndotnet-trace convert cpu-trace.nettrace --format Speedscope\n\n# Opens cpu-trace.speedscope.json -- load at https://www.speedscope.app/\n```\n\n**Using PerfView (Windows, deep .NET integration):**\n\n```bash\n# Convert to Chromium trace format (also viewable in chrome://tracing)\ndotnet-trace convert cpu-trace.nettrace --format Chromium\n```\n\n### Reading Flame Graphs\n\nFlame graphs display call stacks where:\n\n- **Width** of a frame represents the proportion of total sample time spent in that function (wider = more time)\n- **Height** represents call stack depth (taller stacks = deeper call chains)\n- **Color** is typically arbitrary (not meaningful) unless the tool uses a specific color scheme\n\n**Analysis workflow:**\n\n1. Look for **wide plateaus** -- functions that consume a large proportion of samples\n2. Follow the widest frames **upward** to find which callers contribute the most time\n3. Identify **unexpected width** -- framework methods that should be fast appearing wide indicate misuse\n4. Compare **before/after** traces to validate optimizations reduced the width of target functions\n\n**Common patterns in .NET flame graphs:**\n\n| Pattern | Likely Cause | Investigation |\n|---------|-------------|---------------|\n| Wide `System.Linq` frames | LINQ-heavy hot path with delegate overhead | Replace with foreach loops or Span-based processing |\n| Wide `JIT_New` / `gc_heap::allocate` | Excessive allocations triggering GC | Allocation profiling with `--profile gc-collect` |\n| Wide `Monitor.Enter` / `SpinLock` | Lock contention | Review synchronization strategy |\n| Wide `System.Text.RegularExpressions` | Regex backtracking | Use `RegexOptions.NonBacktracking` or compile regex |\n| Deep async state machine frames | Async overhead in tight loops | Consider sync path for CPU-bound work |\n\n### Allocation Tracking with gc-collect Profile\n\nThe `gc-collect` profile captures allocation events to identify what code paths allocate the most memory:\n\n```bash\n# Collect allocation data\ndotnet-trace collect --process-id \u003cPID> \\\n --profile gc-collect \\\n --duration 00:00:30 \\\n --output alloc-trace.nettrace\n```\n\nThis produces a trace that shows:\n\n- Which methods allocate the most bytes\n- Which types are allocated most frequently\n- Allocation sizes and the call stacks that trigger them\n\nCorrelate allocation data with GC counter evidence from dotnet-counters. If `gen-2-gc-count` is high, the allocation trace shows which code paths produce long-lived objects that survive to Gen 2. See `references/performance-patterns.md` for zero-allocation patterns to apply once hot allocation sites are identified.\n\n### Custom Trace Providers\n\nTarget specific event providers for focused tracing:\n\n```bash\n# Trace specific providers with keywords and verbosity\ndotnet-trace collect --process-id \u003cPID> \\\n --providers \"Microsoft-Diagnostics-DiagnosticSource:::FilterAndPayloadSpecs=[AS]System.Net.Http\"\n\n# Trace EF Core queries (useful with [skill:dotnet-api])\ndotnet-trace collect --process-id \u003cPID> \\\n --providers Microsoft.EntityFrameworkCore\n\n# Trace ASP.NET Core request processing\ndotnet-trace collect --process-id \u003cPID> \\\n --providers Microsoft.AspNetCore\n```\n\n### Trace File Management\n\n| Format | Extension | Viewer | Cross-Platform |\n|--------|-----------|--------|----------------|\n| NetTrace | `.nettrace` | PerfView, VS, dotnet-trace convert | Yes (capture); Windows (PerfView) |\n| Speedscope | `.speedscope.json` | https://www.speedscope.app/ | Yes |\n| Chromium | `.chromium.json` | Chrome DevTools (chrome://tracing) | Yes |\n\n\n## dotnet-dump -- Memory Dump Analysis\n\n### Overview\n\n`dotnet-dump` captures and analyzes process memory dumps. Use it to investigate memory leaks, large object heap fragmentation, and object reference chains. Unlike dotnet-trace, dumps capture a point-in-time snapshot of the entire managed heap.\n\n### Capturing Dumps\n\n```bash\n# Capture a full heap dump\ndotnet-dump collect --process-id \u003cPID> --output app-dump.dmp\n\n# Capture a minimal dump (faster, smaller, but less detail)\ndotnet-dump collect --process-id \u003cPID> --type Mini --output app-mini.dmp\n```\n\n**When to capture:**\n\n- Memory usage has grown beyond expected baseline (compare against dotnet-counters `gc-heap-size`)\n- Application is approaching OOM conditions\n- Suspected memory leak after load testing\n- Investigating finalizer queue backlog\n\n### Analyzing Dumps with SOS Commands\n\nOpen the dump in the interactive analyzer:\n\n```bash\ndotnet-dump analyze app-dump.dmp\n```\n\n### !dumpheap -- Heap Object Summary\n\nLists objects on the managed heap grouped by type, sorted by total size:\n\n```\n> dumpheap -stat\n\nStatistics:\n MT Count TotalSize Class Name\n00007fff2c6a4320 125 4,000 System.String[]\n00007fff2c6a1230 8,432 269,824 System.String\n00007fff2c7b5640 2,100 504,000 MyApp.Models.OrderEntity\n00007fff2c6a0988 15,230 1,218,400 System.Byte[]\n```\n\n**Analysis approach:**\n\n1. Look for unexpectedly high counts or sizes for application types\n2. Compare counts against expected cardinality (e.g., 2,100 OrderEntity objects -- is that expected for current load?)\n3. Large `System.Byte[]` counts often indicate unbounded buffering or stream handling issues\n\nFilter by type:\n\n```\n> dumpheap -type MyApp.Models.OrderEntity\n> dumpheap -type System.Byte[] -min 85000\n```\n\nThe `-min 85000` filter shows Large Object Heap entries (objects >= 85,000 bytes that cause Gen 2 GC pressure).\n\n### !gcroot -- Finding Object Retention\n\nTraces the reference chain from a GC root to a specific object, explaining why it is not collected:\n\n```\n> gcroot 00007fff3c4a2100\n\nHandleTable:\n 00007fff3c010010 (strong handle)\n -> 00007fff3c3a1000 MyApp.Services.CacheService\n -> 00007fff3c3a1020 System.Collections.Generic.Dictionary`2\n -> 00007fff3c4a2100 MyApp.Models.OrderEntity\n\nFound 1 unique root(s).\n```\n\n**Common root types and their meaning:**\n\n| Root Type | Meaning | Likely Issue |\n|-----------|---------|-------------|\n| `strong handle` | Static field or GC handle | Static collection growing without eviction |\n| `pinned handle` | Pinned for native interop | Buffer pinned longer than needed |\n| `async state machine` | Captured in async closure | Long-running async operation holding references |\n| `finalizer queue` | Waiting for finalizer thread | Finalizer backlog blocking collection |\n| `threadpool` | Referenced from thread-local storage | Thread-static cache without cleanup |\n\n### !finalizequeue -- Finalizer Queue Analysis\n\nShows objects waiting for finalization, which delays their collection by at least one GC cycle:\n\n```\n> finalizequeue\n\nSyncBlocks to be cleaned up: 0\nFree-Threaded Interfaces to be released: 0\nMTA Interfaces to be released: 0\nSTA Interfaces to be released: 0\n----------------------------------\ngeneration 0 has 12 finalizable objects\ngeneration 1 has 45 finalizable objects\ngeneration 2 has 230 finalizable objects\nReady for finalization 8 objects\n```\n\n**Key indicators:**\n\n- High count in \"Ready for finalization\" means the finalizer thread is falling behind\n- Objects in Gen 2 finalizable list are expensive -- they survive two GC cycles minimum (one to schedule finalization, one to collect after finalization runs)\n- Types implementing `~Destructor()` without `IDisposable.Dispose()` being called are the primary cause\n\n### Additional SOS Commands for Heap Analysis\n\n| Command | Purpose | When to Use |\n|---------|---------|-------------|\n| `dumpobj \u003caddress>` | Display field values of a specific object | Inspect object state after finding it with dumpheap |\n| `dumparray \u003caddress>` | Display array contents | Investigate large arrays found in heap stats |\n| `eeheap -gc` | Show GC heap segment layout | Investigate LOH fragmentation |\n| `gcwhere \u003caddress>` | Show which GC generation holds an object | Determine if an object is pinned or in LOH |\n| `dumpmt \u003cMT>` | Display method table details | Investigate type metadata |\n| `threads` | List all managed threads with stack traces | Identify deadlocks or blocking |\n| `clrstack` | Display managed call stack for current thread | Correlate thread state with heap data |\n\n### Memory Leak Investigation Workflow\n\n1. **Baseline:** Capture a dump after application startup and initial warm-up\n2. **Load:** Run the workload scenario suspected of leaking\n3. **Compare:** Capture a second dump after the workload completes\n4. **Diff:** Compare `dumpheap -stat` output between the two dumps -- look for types whose count or total size grew significantly\n5. **Root:** Use `gcroot` on instances of the growing type to find the retention chain\n6. **Fix:** Break the retention chain (remove from static collections, dispose event subscriptions, fix async lifetime issues)\n\n```bash\n# Tip: save dumpheap output for comparison\n# In dump 1:\n> dumpheap -stat > /tmp/heap-before.txt\n# In dump 2:\n> dumpheap -stat > /tmp/heap-after.txt\n# Compare externally:\n# diff /tmp/heap-before.txt /tmp/heap-after.txt\n```\n\n\n## Profiling Workflow Summary\n\nUse the diagnostic tools in a structured investigation workflow:\n\n```\n1. dotnet-counters (triage)\n ├── CPU high? → dotnet-trace --profile cpu-sampling\n │ → Convert to flame graph (Speedscope)\n │ → Identify hot methods\n ├── Memory growing? → dotnet-dump collect\n │ → dumpheap -stat (find large/numerous types)\n │ → gcroot (find retention chains)\n │ → Fix retention + verify with second dump\n ├── GC pressure? → dotnet-trace --profile gc-collect\n │ → Identify allocation hot paths\n │ → Apply zero-alloc patterns `references/performance-patterns.md`\n └── Thread starvation? → dotnet-dump analyze\n → threads (list all managed threads)\n → clrstack (check for blocking calls)\n```\n\nAfter profiling identifies the bottleneck, use [skill:dotnet-testing] to create targeted benchmarks that quantify the improvement from fixes.\n\n\n## Agent Gotchas\n\n1. **Start with dotnet-counters, not dotnet-trace** -- counters have near-zero overhead and identify the category of problem (CPU, memory, threads). Only reach for trace or dump after counters narrow the investigation.\n2. **Use CPU sampling (not instrumentation) in production** -- sampling overhead is 2-5% and safe for production. Instrumentation adds 10-50%+ overhead and should be limited to development environments.\n3. **Always convert traces to flame graphs for analysis** -- reading raw `.nettrace` event logs is impractical. Use `dotnet-trace convert --format Speedscope` and open in https://www.speedscope.app/ for visual analysis.\n4. **Capture two dumps for leak investigation** -- a single dump shows current state but cannot distinguish normal resident objects from leaked ones. Compare heap statistics across two dumps taken before and after the suspected leak scenario.\n5. **Filter dumpheap by `-min 85000` to find LOH objects** -- objects >= 85,000 bytes go to the Large Object Heap, which is only collected in Gen 2 GC. Large LOH counts indicate potential fragmentation.\n6. **Interpret GC counter data with [skill:dotnet-devops]** -- runtime GC/threadpool counters overlap with OpenTelemetry metrics. Use the observability skill for correlating profiling findings with distributed trace context.\n7. **Do not confuse dotnet-trace gc-collect with dotnet-dump** -- gc-collect traces allocation events over time (which methods allocate); dotnet-dump captures a point-in-time heap snapshot (what objects exist). Use gc-collect for allocation rate analysis; use dotnet-dump for retention/leak analysis.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":17935,"content_sha256":"e6de6018ac97ca9e8a99b35572293d31dad61b99475c8b65ecca4dc57a36f47e"},{"filename":"references/project-analysis.md","content":"# Project Analysis\n\n```bash\nfind . -maxdepth 3 \\( -name \"*.csproj\" -o -name \"*.sln\" -o -name \"*.slnx\" \\) 2>/dev/null | head -20\n```\n\nAnalyzes .NET solution structure, project references, and build configuration. This skill is foundational -- agents need to understand project layout before doing any meaningful .NET development work.\n\n**Prerequisites:** Run `references/version-detection.md` first to determine TFM and SDK version. For .NET 10+ single-file apps without a `.csproj`, see [skill:dotnet-csharp] instead.\n\n## Step 1: Find the Solution Root\n\nLook for solution files in the workspace, starting from the current directory and walking up to the repository root.\n\n### .sln (Legacy Format)\n\nThe traditional MSBuild solution format. Contains project paths and build configurations.\n\n```\nMicrosoft Visual Studio Solution File, Format Version 12.00\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"MyApp\", \"src\\MyApp\\MyApp.csproj\", \"{GUID}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"MyApp.Tests\", \"tests\\MyApp.Tests\\MyApp.Tests.csproj\", \"{GUID}\"\nEndProject\n```\n\nExtract project entries from `Project(\"...\")` lines. The second quoted value is the project name, the third is the relative path to the `.csproj`.\n\n### .slnx (Modern XML Format)\n\nThe new XML-based solution format (supported in .NET 10+ SDK, Visual Studio 17.13+). Preferred for new projects.\n\n```xml\n\u003cSolution>\n \u003cFolder Name=\"/src/\">\n \u003cProject Path=\"src/MyApp/MyApp.csproj\" />\n \u003c/Folder>\n \u003cFolder Name=\"/tests/\">\n \u003cProject Path=\"tests/MyApp.Tests/MyApp.Tests.csproj\" />\n \u003c/Folder>\n\u003c/Solution>\n```\n\nExtract project entries from `\u003cProject Path=\"...\" />` elements. Solution folders (`\u003cFolder>`) indicate logical grouping.\n\n### No Solution File\n\nIf no `.sln` or `.slnx` is found, scan for `.csproj` files recursively. Report: \"No solution file found. Discovered N project files. Consider creating a solution with `dotnet new sln` and `dotnet sln add`.\"\n\n\n## Step 2: Analyze Each Project\n\nFor every `.csproj` discovered in Step 1, read its contents and extract the following.\n\n### Project SDK and Type\n\nThe `\u003cProject Sdk=\"...\">` attribute identifies the project kind:\n\n| SDK | Project Type | Description |\n|-----|-------------|-------------|\n| `Microsoft.NET.Sdk` | Class Library / Console | Default SDK, check for `\u003cOutputType>` |\n| `Microsoft.NET.Sdk.Web` | Web (API / MVC / Razor Pages) | ASP.NET Core web application |\n| `Microsoft.NET.Sdk.BlazorWebAssembly` | Blazor WASM | Client-side Blazor (legacy SDK) |\n| `Microsoft.NET.Sdk.Worker` | Worker Service | Background service / daemon |\n| `Microsoft.NET.Sdk.Razor` | Razor Class Library | Shared Razor components |\n| `Microsoft.Maui.Sdk` or TFMs with `-android`/`-ios` | MAUI | Cross-platform mobile/desktop |\n| Custom or `Uno.Sdk` | Uno Platform | Cross-platform UI (check for Uno references) |\n\n### Output Type Detection\n\nIf SDK is `Microsoft.NET.Sdk`, check `\u003cOutputType>` to distinguish:\n\n| OutputType | Meaning |\n|-----------|---------|\n| `Exe` | Console application |\n| `Library` (or absent) | Class library |\n| `WinExe` | Windows desktop (WPF/WinForms/WinUI) |\n\n### Test Project Detection\n\nA project is a test project if any of the following are true:\n- `\u003cIsTestProject>true\u003c/IsTestProject>` is set\n- Has a PackageReference to `xunit.v3`, `xunit`, `NUnit`, `MSTest.TestFramework`, or `Microsoft.NET.Test.Sdk`\n- Project name ends with `.Tests`, `.UnitTests`, `.IntegrationTests`, or `.TestUtils`\n\n### Blazor Project Detection\n\nA project is Blazor if:\n- SDK is `Microsoft.NET.Sdk.BlazorWebAssembly`\n- Has `\u003cPackageReference Include=\"Microsoft.AspNetCore.Components.WebAssembly\" />`\n- Has `.razor` files in the project directory\n- Uses `AddInteractiveServerComponents()` or `AddInteractiveWebAssemblyComponents()` in startup\n\n### MAUI Project Detection\n\nA project is MAUI if:\n- `\u003cUseMaui>true\u003c/UseMaui>` is set\n- SDK is `Microsoft.Maui.Sdk`\n- TFM includes platform-specific targets: `net*-android`, `net*-ios`, `net*-maccatalyst`, `net*-windows` (e.g., `net8.0-android`, `net10.0-ios`)\n\n### Uno Platform Detection\n\nA project is Uno Platform if:\n- SDK is `Uno.Sdk` or `Uno.Sdk.Private`\n- Has PackageReference to `Uno.WinUI` or `Uno.UI`\n- TFM includes Uno-specific targets (e.g., `net*-browserwasm`, `net*-desktop`)\n\n\n## Step 3: Map Project References\n\nRead `\u003cProjectReference>` elements from each `.csproj` to build the dependency graph.\n\n```xml\n\u003cItemGroup>\n \u003cProjectReference Include=\"..\\MyApp.Core\\MyApp.Core.csproj\" />\n \u003cProjectReference Include=\"..\\MyApp.Infrastructure\\MyApp.Infrastructure.csproj\" />\n\u003c/ItemGroup>\n```\n\nBuild a dependency graph and report it:\n\n```\nProject Dependency Graph\n========================\nMyApp.Web (Web API)\n -> MyApp.Core (Library)\n -> MyApp.Infrastructure (Library)\n -> MyApp.Core (Library)\nMyApp.Tests (Test)\n -> MyApp.Web (Web API)\n -> MyApp.Core (Library)\n```\n\nFlag issues:\n- **Circular references**: \"Project A -> B -> A detected. This will cause build failures.\"\n- **Test projects referencing other test projects**: \"Unusual -- test projects should reference production code, not other tests.\"\n- **Deep nesting**: More than 4 levels deep may indicate over-abstraction.\n\n\n## Step 4: Detect Centralized Build Configuration\n\n### Directory.Build.props\n\nSearch for `Directory.Build.props` starting from each project directory up to the solution root. These files set shared MSBuild properties across all projects in their directory subtree.\n\nCommon shared properties to report:\n- `\u003cTargetFramework>` / `\u003cTargetFrameworks>` -- shared TFM (see `references/version-detection.md`)\n- `\u003cLangVersion>` -- C# language version\n- `\u003cNullable>enable\u003c/Nullable>` -- nullable reference types\n- `\u003cImplicitUsings>enable\u003c/ImplicitUsings>` -- implicit global usings\n- `\u003cTreatWarningsAsErrors>true\u003c/TreatWarningsAsErrors>` -- strict warnings\n- `\u003cEnforceCodeStyleInBuild>true\u003c/EnforceCodeStyleInBuild>` -- code style enforcement\n- `\u003cAnalysisLevel>latest-all\u003c/AnalysisLevel>` -- analyzer severity\n- `\u003cManagePackageVersionsCentrally>true\u003c/ManagePackageVersionsCentrally>` -- CPM indicator\n\nReport: \"Found Directory.Build.props at `\u003cpath>`. Shared settings: [list properties found]. These apply to all projects under `\u003cdirectory>`.\"\n\n### Directory.Build.targets\n\nSearch for `Directory.Build.targets` the same way. These run **after** project evaluation and typically contain:\n- Shared `\u003cPackageReference>` items (e.g., analyzers applied to all projects)\n- Conditional logic based on project type\n- Custom MSBuild targets\n\nReport: \"Found Directory.Build.targets at `\u003cpath>`. Contains: [summarize content].\"\n\n### Multiple Directory.Build Files\n\nIf multiple `Directory.Build.props` files exist at different levels (e.g., root and `src/`), report the hierarchy:\n\n```\nBuild Configuration Hierarchy\n==============================\n/repo/Directory.Build.props (root: Nullable, ImplicitUsings, LangVersion)\n /repo/src/Directory.Build.props (src: TargetFramework, TreatWarningsAsErrors)\n /repo/tests/Directory.Build.props (tests: IsTestProject, test-specific settings)\n```\n\nNote: Inner files do NOT automatically import outer files. Check for `\u003cImport Project=\"$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))\" />` to see if chaining is configured.\n\n\n## Step 5: Detect Central Package Management (CPM)\n\n### Directory.Packages.props\n\nSearch for `Directory.Packages.props` starting from the solution root and walking **upward** toward the repository root (or filesystem root). NuGet resolves CPM hierarchically -- a monorepo may have `Directory.Packages.props` in a parent directory that governs multiple solutions. Also check for `\u003cManagePackageVersionsCentrally>true\u003c/ManagePackageVersionsCentrally>` in any `Directory.Build.props` in the hierarchy, as CPM can be enabled there instead.\n\n```xml\n\u003cProject>\n \u003cPropertyGroup>\n \u003cManagePackageVersionsCentrally>true\u003c/ManagePackageVersionsCentrally>\n \u003c/PropertyGroup>\n \u003cItemGroup>\n \u003cPackageVersion Include=\"Microsoft.Extensions.Logging\" Version=\"10.0.0\" />\n \u003cPackageVersion Include=\"xunit.v3\" Version=\"3.2.2\" />\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\nReport:\n- **CPM enabled**: \"Central Package Management is active. Package versions are defined in `Directory.Packages.props` at `\u003cpath>`. Individual `.csproj` files use `\u003cPackageReference Include=\"...\" />` without `Version` attributes.\"\n- **Package count**: \"N packages managed centrally.\"\n- **Version overrides**: Check for `\u003cPackageReference ... VersionOverride=\"...\">` in individual projects -- flag these as exceptions.\n- **Inherited CPM**: If `Directory.Packages.props` is above the solution root, note: \"CPM is inherited from `\u003cpath>` (above solution root). This is common in monorepos.\"\n\n### CPM Not Used\n\nIf no `Directory.Packages.props` is found in the upward search and `ManagePackageVersionsCentrally` is not set in any `Directory.Build.props`:\n- Report: \"Central Package Management is not configured. Each project defines its own package versions.\"\n- Suggest: \"Consider enabling CPM for version consistency. See `references/project-structure.md` for setup guidance.\"\n\n\n## Step 6: Detect Additional Configuration Files\n\n### .editorconfig\n\nCheck for `.editorconfig` at the solution root and nested levels. Report:\n- Whether it exists\n- Key rules: indent style/size, naming conventions, severity overrides\n- .NET-specific sections: `[*.cs]` rules for `dotnet_style_*` and `csharp_style_*`\n\n### nuget.config\n\nSearch for `nuget.config` (case-insensitive) starting from the solution root and walking **upward** through parent directories. NuGet merges configuration hierarchically (project > user > machine), so multiple files may contribute to the effective config. Report all discovered files and their contents:\n- Package sources configured (e.g., nuget.org, private feeds, local folders)\n- Any `\u003cpackageSourceMapping>` entries (security best practice for supply chain)\n- Any `\u003cdisabledPackageSources>` entries\n- Note: \"User-level (`~/.nuget/NuGet/NuGet.Config`) and machine-level configs may also affect package resolution. Run `dotnet nuget list source` to see the effective merged sources.\"\n\n### global.json\n\nAlready read by `references/version-detection.md`. Report relevant details here:\n- `sdk.version` and `sdk.rollForward` policy\n- `msbuild-sdks` section if present (custom SDK versions)\n\n### .config/dotnet-tools.json\n\nCheck for local tool manifest. Report installed tools:\n- `dotnet-ef` -- Entity Framework Core tools\n- `dotnet-format` -- code formatting\n- `nbgv` -- Nerdbank.GitVersioning\n- Any other tools and their versions\n\n\n## Step 7: Identify Entry Points and Key Files\n\nGuide the agent to the most important files based on project type.\n\n### Web API / MVC / Razor Pages\n- **Entry point**: `Program.cs` (top-level statements, service registration, middleware pipeline)\n- **Configuration**: `appsettings.json`, `appsettings.{Environment}.json`\n- **Startup**: Look for `builder.Services` registrations and `app.Map*` endpoint definitions\n- **Endpoints**: Minimal API endpoints in `Program.cs` or `*.cs` files under an `Endpoints/` directory; Controller-based in `Controllers/` directory\n\n### Console Application\n- **Entry point**: `Program.cs` (top-level statements or `static void Main`)\n- **Configuration**: `appsettings.json` if using `IHostBuilder` / Generic Host\n\n### Worker Service\n- **Entry point**: `Program.cs` with `builder.Services.AddHostedService\u003cWorker>()`\n- **Worker**: Class inheriting `BackgroundService` with `ExecuteAsync` override\n\n### Class Library\n- **No entry point**: Document the public API surface (public classes/interfaces)\n- **Key files**: Look for the primary namespace's types\n\n### Blazor\n- **Entry point**: `Program.cs` with component registration\n- **Root component**: `App.razor` or `Routes.razor`\n- **Layout**: `MainLayout.razor` in `Layout/` or `Shared/`\n- **Pages**: `.razor` files with `@page` directive\n\n### MAUI\n- **Entry point**: `MauiProgram.cs` with `CreateMauiApp()`\n- **App shell**: `AppShell.xaml` for navigation structure\n- **Pages**: Files under `Views/` or `Pages/` directories\n- **Platform-specific**: `Platforms/` directory with Android, iOS, Windows, Mac Catalyst folders\n\n### Test Project\n- **Test files**: `*.cs` files with `[Fact]`, `[Theory]`, `[Test]`, or `[TestMethod]` attributes\n- **Fixtures**: Classes implementing `IClassFixture\u003cT>` or `ICollectionFixture\u003cT>`\n- **Configuration**: Look for `WebApplicationFactory\u003cT>` usage for integration tests\n\n\n## Structured Output Format\n\nAfter completing analysis, present results in this format:\n\n```\n.NET Project Analysis Results\n==============================\nSolution: MyApp.slnx (or MyApp.sln)\nProjects: 5 (2 libraries, 1 web API, 1 console, 1 test)\nCPM: enabled (42 packages in Directory.Packages.props)\nShared Config: Directory.Build.props (Nullable, ImplicitUsings, LangVersion=14)\nCode Style: .editorconfig present\nPackage Sources: nuget.org + private feed (packageSourceMapping configured)\nLocal Tools: dotnet-ef 10.0.0, nbgv 3.7.0\n\nProject Dependency Graph\n------------------------\nMyApp.Api (Web API, net10.0) -> entry: src/MyApp.Api/Program.cs\n -> MyApp.Core (Library)\n -> MyApp.Infrastructure (Library)\n -> MyApp.Core (Library)\nMyApp.Console (Console, net10.0) -> entry: src/MyApp.Console/Program.cs\n -> MyApp.Core (Library)\nMyApp.Tests (Test, xUnit) -> entry: tests/MyApp.Tests/\n -> MyApp.Api (Web API)\n -> MyApp.Core (Library)\n\nKey Files\n---------\n- Solution root: /repo/MyApp.slnx\n- Shared props: /repo/Directory.Build.props\n- Package versions: /repo/Directory.Packages.props\n- API entry point: /repo/src/MyApp.Api/Program.cs\n- API config: /repo/src/MyApp.Api/appsettings.json\n```\n\n\n## Edge Cases\n\n### Mixed Solution Formats\nIf both `.sln` and `.slnx` exist, prefer `.slnx` (modern format). Note: \"Both `.sln` and `.slnx` found. Using `.slnx` as primary. The `.sln` may be maintained for older tooling compatibility.\"\n\n### Monorepo with Multiple Solutions\nIf multiple `.sln`/`.slnx` files exist, report all of them and ask the user which solution to analyze. If one is at the repository root, default to that one.\n\n### Projects Not in Solution\nIf `.csproj` files exist that are not referenced by any solution file, report them as orphaned: \"Found N project files not included in any solution. These may be experimental or unused.\"\n\n### Conditional ProjectReferences\nIf `\u003cProjectReference>` is inside a `\u003cWhen>` or has a `Condition` attribute:\n```xml\n\u003cProjectReference Include=\"..\\MyApp.DevTools\\MyApp.DevTools.csproj\"\n Condition=\"'$(Configuration)' == 'Debug'\" />\n```\nReport: \"Conditional reference to MyApp.DevTools (Debug only).\"\n\n### Web Project Without launchSettings.json\nIf a web project has no `Properties/launchSettings.json`, note: \"No `launchSettings.json` found. The project uses default Kestrel settings. Consider adding launch profiles for development.\"\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":14912,"content_sha256":"68bf561c9b496d22fddf1a160e96fe055ea743396ac1cb828daa73caf944a1d2"},{"filename":"references/project-structure.md","content":"# Project Structure\n\nReference guide for modern .NET project structure and solution layout. Use when creating new solutions, reviewing existing structure, or recommending improvements.\n\n**Prerequisites:** Run `references/version-detection.md` first to determine TFM and SDK version — this affects which features are available (e.g., .slnx requires .NET 9+ SDK).\n\n## Recommended Solution Layout\n\n```\nMyApp/\n├── .editorconfig\n├── .gitignore\n├── global.json\n├── nuget.config\n├── Directory.Build.props\n├── Directory.Build.targets\n├── Directory.Packages.props\n├── MyApp.slnx # .NET 9+ SDK / VS 17.13+\n├── src/\n│ ├── MyApp.Core/\n│ │ └── MyApp.Core.csproj\n│ ├── MyApp.Api/\n│ │ ├── MyApp.Api.csproj\n│ │ ├── Program.cs\n│ │ └── appsettings.json\n│ └── MyApp.Infrastructure/\n│ └── MyApp.Infrastructure.csproj\n└── tests/\n ├── MyApp.UnitTests/\n │ └── MyApp.UnitTests.csproj\n └── MyApp.IntegrationTests/\n └── MyApp.IntegrationTests.csproj\n```\n\nKey principles:\n- Separate `src/` and `tests/` directories\n- One project per concern (Core/Domain, Infrastructure, API/Host)\n- Solution file at the repo root\n- All shared build configuration at the repo root\n\n\n## Solution File Formats\n\n### .slnx (Modern — .NET 9+)\n\nThe XML-based solution format is human-readable and diff-friendly. Requires .NET 9+ SDK or Visual Studio 17.13+.\n\n```xml\n\u003cSolution>\n \u003cFolder Name=\"/src/\">\n \u003cProject Path=\"src/MyApp.Core/MyApp.Core.csproj\" />\n \u003cProject Path=\"src/MyApp.Api/MyApp.Api.csproj\" />\n \u003cProject Path=\"src/MyApp.Infrastructure/MyApp.Infrastructure.csproj\" />\n \u003c/Folder>\n \u003cFolder Name=\"/tests/\">\n \u003cProject Path=\"tests/MyApp.UnitTests/MyApp.UnitTests.csproj\" />\n \u003cProject Path=\"tests/MyApp.IntegrationTests/MyApp.IntegrationTests.csproj\" />\n \u003c/Folder>\n\u003c/Solution>\n```\n\nConvert existing `.sln` to `.slnx`:\n\n```bash\ndotnet sln MyApp.sln migrate\n```\n\n### .sln (Legacy — All Versions)\n\nThe traditional format remains the fallback for older tooling, CI agents, and third-party integrations that don't support `.slnx` yet. Keep `.sln` alongside `.slnx` during the transition period if needed.\n\n```bash\ndotnet new sln -n MyApp\ndotnet sln add src/**/*.csproj\ndotnet sln add tests/**/*.csproj\n```\n\n\n## Directory.Build.props\n\nShared MSBuild properties applied to all projects in the directory subtree. Place at the repo root.\n\n```xml\n\u003cProject>\n \u003cPropertyGroup>\n \u003cTargetFramework>net10.0\u003c/TargetFramework>\n \u003cLangVersion>14\u003c/LangVersion>\n \u003cNullable>enable\u003c/Nullable>\n \u003cImplicitUsings>enable\u003c/ImplicitUsings>\n \u003cTreatWarningsAsErrors>true\u003c/TreatWarningsAsErrors>\n \u003cEnforceCodeStyleInBuild>true\u003c/EnforceCodeStyleInBuild>\n \u003cAnalysisLevel>latest-all\u003c/AnalysisLevel>\n \u003c/PropertyGroup>\n\u003c/Project>\n```\n\n### Nested Directory.Build.props\n\nInner files do **not** automatically import outer files. To chain them:\n\n```xml\n\u003c!-- src/Directory.Build.props -->\n\u003cProject>\n \u003cImport Project=\"$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))\" />\n \u003cPropertyGroup>\n \u003c!-- src-specific settings -->\n \u003c/PropertyGroup>\n\u003c/Project>\n```\n\nCommon pattern: separate props for src vs tests:\n\n```\nrepo/\n├── Directory.Build.props # Shared: LangVersion, Nullable, ImplicitUsings\n├── src/\n│ └── Directory.Build.props # Imports parent + adds TreatWarningsAsErrors\n└── tests/\n └── Directory.Build.props # Imports parent + sets IsTestProject\n```\n\n\n## Directory.Build.targets\n\nImported **after** project evaluation. Use for:\n- Shared analyzer package references\n- Custom build targets\n- Conditional logic based on project type\n\n```xml\n\u003cProject>\n \u003c!-- Apply analyzers to all projects -->\n \u003cItemGroup>\n \u003cPackageReference Include=\"Meziantou.Analyzer\" PrivateAssets=\"all\" />\n \u003cPackageReference Include=\"Microsoft.CodeAnalysis.BannedApiAnalyzers\" PrivateAssets=\"all\" />\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\n\n## Central Package Management (CPM)\n\nCPM centralizes all NuGet package versions in `Directory.Packages.props` at the repo root. Individual `.csproj` files reference packages **without** a `Version` attribute.\n\n### Directory.Packages.props\n\n```xml\n\u003cProject>\n \u003cPropertyGroup>\n \u003cManagePackageVersionsCentrally>true\u003c/ManagePackageVersionsCentrally>\n \u003cCentralPackageTransitivePinningEnabled>true\u003c/CentralPackageTransitivePinningEnabled>\n \u003c/PropertyGroup>\n \u003cItemGroup>\n \u003c!-- Shared dependencies -->\n \u003cPackageVersion Include=\"Microsoft.Extensions.Logging\" Version=\"10.0.0\" />\n \u003cPackageVersion Include=\"System.Text.Json\" Version=\"10.0.0\" />\n \u003c/ItemGroup>\n \u003cItemGroup>\n \u003c!-- Test dependencies -->\n \u003cPackageVersion Include=\"xunit.v3\" Version=\"3.2.2\" />\n \u003cPackageVersion Include=\"xunit.runner.visualstudio\" Version=\"3.1.5\" />\n \u003cPackageVersion Include=\"coverlet.collector\" Version=\"8.0.0\" />\n \u003cPackageVersion Include=\"Microsoft.NET.Test.Sdk\" Version=\"18.0.1\" />\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\n### Project File with CPM\n\n```xml\n\u003cProject Sdk=\"Microsoft.NET.Sdk\">\n \u003cItemGroup>\n \u003c!-- No Version attribute — managed centrally -->\n \u003cPackageReference Include=\"Microsoft.Extensions.Logging\" />\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\n### Version Overrides\n\nWhen a specific project needs a different version (rare), use `VersionOverride`:\n\n```xml\n\u003cPackageReference Include=\"Newtonsoft.Json\" VersionOverride=\"13.0.3\" />\n```\n\nFlag version overrides during code review — they defeat the purpose of CPM.\n\n\n## .editorconfig\n\nPlace at the repo root to enforce consistent code style across all editors and the build.\n\n```ini\nroot = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.{csproj,props,targets,xml,json,yml,yaml}]\nindent_size = 2\n\n[*.cs]\n# Namespace declarations\ncsharp_style_namespace_declarations = file_scoped:warning\n\n# Braces\ncsharp_prefer_braces = true:warning\n\n# var preferences\ncsharp_style_var_for_built_in_types = true:suggestion\ncsharp_style_var_when_type_is_apparent = true:suggestion\ncsharp_style_var_elsewhere = true:suggestion\n\n# Access modifiers\ndotnet_style_require_accessibility_modifiers = always:warning\n\n# Pattern matching\ncsharp_style_prefer_pattern_matching = true:suggestion\ncsharp_style_prefer_switch_expression = true:suggestion\n\n# Null checking\ncsharp_style_prefer_null_check_over_type_check = true:suggestion\ndotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning\n\n# Expression-level preferences\ncsharp_style_expression_bodied_methods = when_on_single_line:suggestion\ncsharp_style_expression_bodied_properties = true:suggestion\n\n# Using directives\ncsharp_using_directive_placement = outside_namespace:warning\ndotnet_sort_system_directives_first = true\n\n# Naming conventions\ndotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fields\ndotnet_naming_rule.private_fields_should_be_camel_case.style = camel_case_underscore\ndotnet_naming_rule.private_fields_should_be_camel_case.severity = warning\ndotnet_naming_symbols.private_fields.applicable_kinds = field\ndotnet_naming_symbols.private_fields.applicable_accessibilities = private\ndotnet_naming_style.camel_case_underscore.required_prefix = _\ndotnet_naming_style.camel_case_underscore.capitalization = camel_case\n```\n\nSee `references/add-analyzers.md` for full analyzer rule configuration.\n\n\n## global.json\n\nPin the SDK version for reproducible builds:\n\n```json\n{\n \"sdk\": {\n \"version\": \"10.0.100\",\n \"rollForward\": \"latestPatch\"\n }\n}\n```\n\nRoll-forward policies:\n- `latestPatch` — allow patch updates only (recommended for CI)\n- `latestFeature` — allow feature-band updates within the major version\n- `latestMajor` — use whatever is installed (development convenience, not for CI)\n- `disable` — exact version only\n\n\n## nuget.config\n\nConfigure package sources and security:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003cconfiguration>\n \u003cpackageSources>\n \u003cclear />\n \u003cadd key=\"nuget.org\" value=\"https://api.nuget.org/v3/index.json\" />\n \u003c/packageSources>\n \u003cpackageSourceMapping>\n \u003cpackageSource key=\"nuget.org\">\n \u003cpackage pattern=\"*\" />\n \u003c/packageSource>\n \u003c/packageSourceMapping>\n\u003c/configuration>\n```\n\nThe `\u003cclear />` + explicit sources + `\u003cpackageSourceMapping>` pattern prevents supply-chain attacks by ensuring packages only come from expected sources.\n\nFor private feeds, map internal package prefixes exclusively to the private source:\n\n```xml\n\u003cpackageSources>\n \u003cclear />\n \u003cadd key=\"nuget.org\" value=\"https://api.nuget.org/v3/index.json\" />\n \u003cadd key=\"internal\" value=\"https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json\" />\n\u003c/packageSources>\n\u003cpackageSourceMapping>\n \u003cpackageSource key=\"nuget.org\">\n \u003cpackage pattern=\"*\" />\n \u003c/packageSource>\n \u003cpackageSource key=\"internal\">\n \u003cpackage pattern=\"MyCompany.*\" />\n \u003c/packageSource>\n\u003c/packageSourceMapping>\n```\n\nNuGet uses **most-specific-pattern-wins** precedence: `MyCompany.Foo` matches `MyCompany.*` (internal) over `*` (nuget.org), so internal packages restore exclusively from the private feed. This prevents dependency confusion attacks — an attacker cannot squat `MyCompany.Foo` on nuget.org because NuGet will never look there for packages matching `MyCompany.*`.\n\n**Do not** map the same prefix to multiple sources unless you trust both — that defeats the protection.\n\n\n## NuGet Audit\n\n.NET 9+ enables `NuGetAudit` by default, which checks for known vulnerabilities during restore. Configure the severity threshold:\n\n```xml\n\u003c!-- In Directory.Build.props -->\n\u003cPropertyGroup>\n \u003cNuGetAudit>true\u003c/NuGetAudit>\n \u003cNuGetAuditLevel>low\u003c/NuGetAuditLevel>\n \u003cNuGetAuditMode>all\u003c/NuGetAuditMode> \u003c!-- audit direct + transitive -->\n\u003c/PropertyGroup>\n```\n\n\n## Lock Files\n\nEnable deterministic restores with lock files:\n\n```xml\n\u003c!-- In Directory.Build.props -->\n\u003cPropertyGroup>\n \u003cRestorePackagesWithLockFile>true\u003c/RestorePackagesWithLockFile>\n\u003c/PropertyGroup>\n```\n\nThis generates `packages.lock.json` per project. Commit these files. In CI, restore with `--locked-mode`:\n\n```bash\ndotnet restore --locked-mode\n```\n\n\n## SourceLink and Deterministic Builds\n\nFor libraries published to NuGet:\n\n```xml\n\u003c!-- In Directory.Build.props -->\n\u003cPropertyGroup>\n \u003cPublishRepositoryUrl>true\u003c/PublishRepositoryUrl>\n \u003cEmbedUntrackedSources>true\u003c/EmbedUntrackedSources>\n \u003cDebugType>embedded\u003c/DebugType>\n \u003cContinuousIntegrationBuild Condition=\"'$(CI)' == 'true'\">true\u003c/ContinuousIntegrationBuild>\n\u003c/PropertyGroup>\n\u003cItemGroup>\n \u003cPackageReference Include=\"Microsoft.SourceLink.GitHub\" PrivateAssets=\"all\" />\n\u003c/ItemGroup>\n```\n\nKey properties:\n- `PublishRepositoryUrl` — includes the repo URL in the NuGet package\n- `EmbedUntrackedSources` — embeds generated source files\n- `DebugType=embedded` — PDB embedded in the assembly (no separate symbol package needed)\n- `ContinuousIntegrationBuild` — enables deterministic paths (only in CI to avoid breaking local debugging)\n\n\n## References\n\n- [.NET Library Design Guidance](https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/)\n- [Central Package Management](https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management)\n- [.slnx Format](https://learn.microsoft.com/en-us/visualstudio/ide/reference/solution-file)\n- [Directory.Build.props](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build)\n- [SourceLink](https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/sourcelink)\n- [NuGet Audit](https://learn.microsoft.com/en-us/nuget/concepts/auditing-packages)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":11726,"content_sha256":"584aaf9bf6a5c6efc1fc1ec6e65d2ca3a9590f6853d1f4f7fc6837f756c95101"},{"filename":"references/scaffold-project.md","content":"# Scaffold Project\n\nScaffolds a new .NET project with all modern best practices applied. Generates the full solution structure including Central Package Management, analyzers, .editorconfig, SourceLink, and deterministic builds.\n\n**Prerequisites:** Run `references/version-detection.md` first to determine available SDK version — this affects which features and templates are available.\n\n## Step 1: Create Solution Structure\n\nCreate the directory layout and solution file.\n\n```bash\n# Create the directory structure\nmkdir -p MyApp/src MyApp/tests\n\n# Create solution file\ncd MyApp\ndotnet new sln -n MyApp\n\n# For .NET 9+ SDK, convert to .slnx\ndotnet sln MyApp.sln migrate\n```\n\n### Choose Project Template\n\nSelect the appropriate template based on the application type:\n\n| Template | Command | SDK |\n|----------|---------|-----|\n| Web API (minimal) | `dotnet new webapi -n MyApp.Api -o src/MyApp.Api` | `Microsoft.NET.Sdk.Web` |\n| Web API (controllers) | `dotnet new webapi -n MyApp.Api -o src/MyApp.Api --use-controllers` | `Microsoft.NET.Sdk.Web` |\n| Console app | `dotnet new console -n MyApp.Cli -o src/MyApp.Cli` | `Microsoft.NET.Sdk` |\n| Worker service | `dotnet new worker -n MyApp.Worker -o src/MyApp.Worker` | `Microsoft.NET.Sdk.Worker` |\n| Class library | `dotnet new classlib -n MyApp.Core -o src/MyApp.Core` | `Microsoft.NET.Sdk` |\n| Blazor web app | `dotnet new blazor -n MyApp.Web -o src/MyApp.Web` | `Microsoft.NET.Sdk.Web` |\n| MAUI app | `dotnet new maui -n MyApp.Mobile -o src/MyApp.Mobile` | `Microsoft.Maui.Sdk` |\n| xUnit test | `dotnet new xunit -n MyApp.Tests -o tests/MyApp.Tests` | `Microsoft.NET.Sdk` |\n\n```bash\n# Example: Web API with class library and tests\ndotnet new classlib -n MyApp.Core -o src/MyApp.Core\ndotnet new webapi -n MyApp.Api -o src/MyApp.Api\ndotnet new xunit -n MyApp.UnitTests -o tests/MyApp.UnitTests\n\n# Add projects to solution\ndotnet sln add src/MyApp.Core/MyApp.Core.csproj\ndotnet sln add src/MyApp.Api/MyApp.Api.csproj\ndotnet sln add tests/MyApp.UnitTests/MyApp.UnitTests.csproj\n\n# Add project references\ndotnet add src/MyApp.Api/MyApp.Api.csproj reference src/MyApp.Core/MyApp.Core.csproj\ndotnet add tests/MyApp.UnitTests/MyApp.UnitTests.csproj reference src/MyApp.Core/MyApp.Core.csproj\n```\n\n\n## Step 2: Add global.json\n\nPin the SDK version for reproducible builds.\n\n```json\n{\n \"sdk\": {\n \"version\": \"10.0.100\",\n \"rollForward\": \"latestPatch\"\n }\n}\n```\n\nAdjust the version to match the output of `dotnet --version`.\n\n\n## Step 3: Add Directory.Build.props\n\nCreate at the repo root to share build settings across all projects.\n\n```xml\n\u003cProject>\n \u003cPropertyGroup>\n \u003cTargetFramework>net10.0\u003c/TargetFramework>\n \u003cLangVersion>14\u003c/LangVersion>\n \u003cNullable>enable\u003c/Nullable>\n \u003cImplicitUsings>enable\u003c/ImplicitUsings>\n \u003cTreatWarningsAsErrors>true\u003c/TreatWarningsAsErrors>\n \u003cEnforceCodeStyleInBuild>true\u003c/EnforceCodeStyleInBuild>\n \u003cAnalysisLevel>latest-all\u003c/AnalysisLevel>\n \u003c/PropertyGroup>\n\n \u003c!-- Deterministic builds and SourceLink (for libraries) -->\n \u003cPropertyGroup>\n \u003cPublishRepositoryUrl>true\u003c/PublishRepositoryUrl>\n \u003cEmbedUntrackedSources>true\u003c/EmbedUntrackedSources>\n \u003cDebugType>embedded\u003c/DebugType>\n \u003cContinuousIntegrationBuild Condition=\"'$(CI)' == 'true'\">true\u003c/ContinuousIntegrationBuild>\n \u003c/PropertyGroup>\n\n \u003c!-- NuGet audit -->\n \u003cPropertyGroup>\n \u003cNuGetAudit>true\u003c/NuGetAudit>\n \u003cNuGetAuditLevel>low\u003c/NuGetAuditLevel>\n \u003cNuGetAuditMode>all\u003c/NuGetAuditMode>\n \u003cRestorePackagesWithLockFile>true\u003c/RestorePackagesWithLockFile>\n \u003c/PropertyGroup>\n\u003c/Project>\n```\n\nAfter creating this, **remove** `\u003cTargetFramework>`, `\u003cNullable>`, and `\u003cImplicitUsings>` from individual `.csproj` files to avoid duplication.\n\n### Optional: Separate Test Props\n\n```xml\n\u003c!-- tests/Directory.Build.props -->\n\u003cProject>\n \u003cImport Project=\"$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))\" />\n \u003cPropertyGroup>\n \u003cIsPackable>false\u003c/IsPackable>\n \u003cIsTestProject>true\u003c/IsTestProject>\n \u003c!-- Use Microsoft.Testing.Platform v2 runner (requires Microsoft.NET.Test.Sdk 17.13+/18.x) -->\n \u003cUseMicrosoftTestingPlatformRunner>true\u003c/UseMicrosoftTestingPlatformRunner>\n \u003c!-- Tests don't need TreatWarningsAsErrors -->\n \u003cTreatWarningsAsErrors>false\u003c/TreatWarningsAsErrors>\n \u003c/PropertyGroup>\n\u003c/Project>\n```\n\n\n## Step 4: Add Directory.Build.targets\n\nApply shared package references (SourceLink, analyzers) to all projects. Items go in `.targets` so they are imported after project evaluation.\n\n```xml\n\u003cProject>\n \u003cItemGroup>\n \u003c!-- SourceLink for debugger source navigation -->\n \u003cPackageReference Include=\"Microsoft.SourceLink.GitHub\" PrivateAssets=\"all\" />\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\nThe built-in Roslyn analyzers are already enabled by the `AnalysisLevel` and `EnforceCodeStyleInBuild` properties in Directory.Build.props (Step 3). For additional third-party analyzers, see `references/add-analyzers.md`.\n\n\n## Step 5: Set Up Central Package Management\n\nCreate `Directory.Packages.props` at the repo root.\n\n```xml\n\u003cProject>\n \u003cPropertyGroup>\n \u003cManagePackageVersionsCentrally>true\u003c/ManagePackageVersionsCentrally>\n \u003cCentralPackageTransitivePinningEnabled>true\u003c/CentralPackageTransitivePinningEnabled>\n \u003c/PropertyGroup>\n \u003cItemGroup>\n \u003c!-- Framework packages -->\n \u003cPackageVersion Include=\"Microsoft.SourceLink.GitHub\" Version=\"9.0.0\" />\n \u003c/ItemGroup>\n \u003cItemGroup>\n \u003c!-- Test packages -->\n \u003cPackageVersion Include=\"Microsoft.NET.Test.Sdk\" Version=\"18.0.1\" />\n \u003cPackageVersion Include=\"xunit.v3\" Version=\"3.2.2\" />\n \u003cPackageVersion Include=\"xunit.runner.visualstudio\" Version=\"3.1.5\" />\n \u003cPackageVersion Include=\"coverlet.collector\" Version=\"8.0.0\" />\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\nAfter creating this, **remove** `Version` attributes from all `\u003cPackageReference>` elements in `.csproj` files.\n\n\n## Step 6: Add .editorconfig\n\nCreate at the repo root. See `references/project-structure.md` for the full recommended config.\n\nMinimal starter:\n\n```ini\nroot = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.{csproj,props,targets,xml,json,yml,yaml}]\nindent_size = 2\n\n[*.cs]\ncsharp_style_namespace_declarations = file_scoped:warning\ncsharp_prefer_braces = true:warning\ndotnet_style_require_accessibility_modifiers = always:warning\ndotnet_sort_system_directives_first = true\ncsharp_using_directive_placement = outside_namespace:warning\n```\n\n\n## Step 7: Add nuget.config\n\nConfigure package sources with supply-chain security:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003cconfiguration>\n \u003cpackageSources>\n \u003cclear />\n \u003cadd key=\"nuget.org\" value=\"https://api.nuget.org/v3/index.json\" />\n \u003c/packageSources>\n \u003cpackageSourceMapping>\n \u003cpackageSource key=\"nuget.org\">\n \u003cpackage pattern=\"*\" />\n \u003c/packageSource>\n \u003c/packageSourceMapping>\n\u003c/configuration>\n```\n\n\n## Step 8: Add .gitignore\n\n```bash\ndotnet new gitignore\n```\n\nThis generates the standard .NET `.gitignore` covering `bin/`, `obj/`, `*.user`, etc.\n\n\n## Step 9: Clean Up Generated Projects\n\nAfter scaffolding, apply the shared configuration:\n\n1. **Remove duplicated properties** from individual `.csproj` files (TargetFramework, Nullable, ImplicitUsings — these are in Directory.Build.props)\n2. **Remove Version attributes** from PackageReference elements (managed by CPM)\n3. **Delete template-generated Class1.cs** from class libraries\n4. **Set file-scoped namespaces** in all generated `.cs` files\n\n### Cleaned csproj Example\n\nBefore (template-generated):\n```xml\n\u003cProject Sdk=\"Microsoft.NET.Sdk\">\n \u003cPropertyGroup>\n \u003cTargetFramework>net10.0\u003c/TargetFramework>\n \u003cNullable>enable\u003c/Nullable>\n \u003cImplicitUsings>enable\u003c/ImplicitUsings>\n \u003c/PropertyGroup>\n\u003c/Project>\n```\n\nAfter (with shared props and CPM):\n```xml\n\u003cProject Sdk=\"Microsoft.NET.Sdk\">\n\u003c/Project>\n```\n\nFor web projects that need `Microsoft.NET.Sdk.Web`, the csproj still specifies the SDK but inherits everything else.\n\n\n## Step 10: Verify\n\nRun these commands to verify the scaffolded project:\n\n```bash\n# Restore and verify lock files generated\ndotnet restore\nfind . -name \"packages.lock.json\" -type f\n\n# Build with all analyzers\ndotnet build --no-restore\n\n# Run tests\ndotnet test --no-build\n\n# Verify CPM is active (no Version attributes in project PackageReferences)\n# Should only find versions in Directory.Packages.props, not in csproj files\nfind . -name \"*.csproj\" -exec grep -l 'Version=' {} \\; # expect no output\n```\n\n\n## Final Structure\n\n```\nMyApp/\n├── .editorconfig\n├── .gitignore\n├── global.json\n├── nuget.config\n├── MyApp.slnx\n├── Directory.Build.props\n├── Directory.Build.targets\n├── Directory.Packages.props\n├── src/\n│ ├── MyApp.Core/\n│ │ └── MyApp.Core.csproj\n│ └── MyApp.Api/\n│ ├── MyApp.Api.csproj\n│ ├── Program.cs\n│ └── appsettings.json\n└── tests/\n └── MyApp.UnitTests/\n ├── MyApp.UnitTests.csproj\n └── SampleTest.cs\n```\n\n\n## References\n\n- [.NET Library Design Guidance](https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/)\n- [Central Package Management](https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management)\n- [SourceLink](https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/sourcelink)\n- [NuGet Audit](https://learn.microsoft.com/en-us/nuget/concepts/auditing-packages)\n- [dotnet new Templates](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-new)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":9592,"content_sha256":"d1588d3a9a4b1661b5b65c9750e18d58667997d5c13458ad85c74835845c5030"},{"filename":"references/solution-navigation.md","content":"# Solution Navigation\n\n```bash\nfind . -maxdepth 2 \\( -name \"*.sln\" -o -name \"*.slnx\" \\) 2>/dev/null | head -5\n```\n\nTeaches agents to orient in .NET solutions: finding entry points, parsing solution files, traversing project dependencies, locating configuration files, and recognizing common solution layouts. Each subsection includes discovery commands/heuristics and example output.\n\n## Prerequisites\n\n.NET 8.0+ SDK. `dotnet` CLI available on PATH. Familiarity with SDK-style projects.\n\nCross-references: `references/project-structure.md` for project organization guidance, `references/csproj-reading.md` for reading and modifying .csproj files found during navigation, [skill:dotnet-testing] for test project identification and test type decisions.\n\n\n## Subsection 1: Entry Point Discovery\n\n.NET applications can start from several patterns. Do not assume every app has a traditional `Program.cs` with a `Main` method.\n\n### Pattern 1: Traditional Program.cs with Main Method\n\nUsed in older projects, worker services, and when explicit control over hosting is needed.\n\n**Discovery command:**\n\n```bash\n# Find Program.cs files containing a Main method\ngrep -rn \"static.*void Main\\|static.*Task Main\\|static.*async.*Main\" --include=\"*.cs\" .\n```\n\n**Example output:**\n\n```\nsrc/MyApp.Worker/Program.cs:5: public static async Task Main(string[] args)\nsrc/MyApp.Console/Program.cs:3: static void Main(string[] args)\n```\n\n### Pattern 2: Top-Level Statements (C# 9+)\n\nModern .NET projects (templates since .NET 6) use top-level statements -- the file contains no class or Main method, just executable code.\n\n**Discovery command:**\n\n```bash\n# Find Program.cs files that do NOT contain class/namespace declarations\n# (top-level statements have no enclosing class)\nfor f in $(find . -name \"Program.cs\" -not -path \"*/obj/*\" -not -path \"*/bin/*\"); do\n if ! grep -Eq '^[[:space:]]*(class|namespace)[[:space:]]' \"$f\" 2>/dev/null; then\n echo \"Top-level: $f\"\n fi\ndone\n```\n\n**Example output:**\n\n```\nTop-level: ./src/MyApp.Api/Program.cs\nTop-level: ./src/MyApp.Web/Program.cs\n```\n\n**Typical content of a top-level Program.cs:**\n\n```csharp\n// No namespace, no class, no Main -- this IS the entry point\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddControllers();\nvar app = builder.Build();\napp.MapControllers();\napp.Run();\n```\n\n### Pattern 3: Worker Services and Background Hosts\n\nWorker services use `Host.CreateDefaultBuilder` or `Host.CreateApplicationBuilder` without a web server. They appear as `Exe` output type with `Microsoft.NET.Sdk.Worker` SDK.\n\n**Discovery command:**\n\n```bash\n# Find worker service projects by SDK type\ngrep -rn 'Sdk=\"Microsoft.NET.Sdk.Worker\"' --include=\"*.csproj\" .\n\n# Or find IHostedService/BackgroundService implementations\ngrep -rn \"BackgroundService\\|IHostedService\" --include=\"*.cs\" . | grep -v \"obj/\" | grep -v \"bin/\"\n```\n\n**Example output:**\n\n```\nsrc/MyApp.Worker/MyApp.Worker.csproj:1:\u003cProject Sdk=\"Microsoft.NET.Sdk.Worker\">\nsrc/MyApp.Worker/Services/OrderProcessor.cs:8:public class OrderProcessor : BackgroundService\nsrc/MyApp.Worker/Services/EmailSender.cs:5:public class EmailSender : IHostedService\n```\n\n### Pattern 4: Test Projects\n\nTest projects are entry points for `dotnet test`. They may not have a `Program.cs` at all -- the test runner provides the entry point.\n\n**Discovery command:**\n\n```bash\n# Find test projects by IsTestProject property or test SDK references\ngrep -rn \"\u003cIsTestProject>true\u003c/IsTestProject>\" --include=\"*.csproj\" .\ngrep -rn \"Microsoft.NET.Test.Sdk\\|xunit\\|NUnit\\|MSTest\" --include=\"*.csproj\" . | grep -v \"obj/\" # Matches both xunit.v3 and legacy xunit\n```\n\n**Example output:**\n\n```\ntests/MyApp.Api.Tests/MyApp.Api.Tests.csproj:5: \u003cIsTestProject>true\u003c/IsTestProject>\ntests/MyApp.Core.Tests/MyApp.Core.Tests.csproj:8: \u003cPackageReference Include=\"xunit.v3\" />\n```\n\n### Summary Heuristic\n\nWhen orienting in a new .NET solution, run these commands in sequence:\n\n```bash\n# 1. Find all .csproj files\nfind . -name \"*.csproj\" -not -path \"*/obj/*\" | sort\n\n# 2. Identify output types (Exe = app entry point, Library = dependency)\ngrep -rn \"\u003cOutputType>\" --include=\"*.csproj\" .\n\n# 3. Find all Program.cs files\nfind . -name \"Program.cs\" -not -path \"*/obj/*\" -not -path \"*/bin/*\"\n\n# 4. Identify test projects\ngrep -rn \"\u003cIsTestProject>true\" --include=\"*.csproj\" .\n```\n\n\n## Subsection 2: Solution File Formats\n\n.NET solutions use `.sln` (text-based, legacy format) or `.slnx` (XML-based, .NET 9+ preview). Both files list projects and their relationships.\n\n### .sln Format\n\nThe traditional solution format is a text file with a custom syntax (not XML).\n\n**Discovery and parsing commands:**\n\n```bash\n# Find solution files\nfind . -name \"*.sln\" -maxdepth 2\n\n# List all projects in a solution using dotnet CLI\ndotnet sln list\n# Or specify the solution file explicitly:\ndotnet sln MyApp.sln list\n```\n\n**Example output of `dotnet sln list`:**\n\n```\nProject(s)\n----------\nsrc/MyApp.Api/MyApp.Api.csproj\nsrc/MyApp.Core/MyApp.Core.csproj\nsrc/MyApp.Infrastructure/MyApp.Infrastructure.csproj\ntests/MyApp.Api.Tests/MyApp.Api.Tests.csproj\ntests/MyApp.Core.Tests/MyApp.Core.Tests.csproj\n```\n\n**Reading the .sln file directly** (useful when `dotnet sln list` is not available):\n\n```bash\n# Extract project entries from .sln file\ngrep \"^Project(\" MyApp.sln\n```\n\n**Example output:**\n\n```\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"MyApp.Api\", \"src\\MyApp.Api\\MyApp.Api.csproj\", \"{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}\"\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"MyApp.Core\", \"src\\MyApp.Core\\MyApp.Core.csproj\", \"{B2C3D4E5-F6A7-8901-BCDE-F12345678901}\"\n```\n\nThe GUID `{FAE04EC0-...}` identifies C# projects. The second value is the relative path to the `.csproj` file.\n\n### .slnx Format (.NET 9+)\n\nThe `.slnx` format is an XML-based solution file introduced as a preview feature in .NET 9.\n\n**Discovery and parsing commands:**\n\n```bash\n# Find .slnx files\nfind . -name \"*.slnx\" -maxdepth 2\n\n# dotnet sln commands work with .slnx files too\ndotnet sln MyApp.slnx list\n```\n\n**Example .slnx content:**\n\n```xml\n\u003cSolution>\n \u003cFolder Name=\"/src/\">\n \u003cProject Path=\"src/MyApp.Api/MyApp.Api.csproj\" />\n \u003cProject Path=\"src/MyApp.Core/MyApp.Core.csproj\" />\n \u003c/Folder>\n \u003cFolder Name=\"/tests/\">\n \u003cProject Path=\"tests/MyApp.Api.Tests/MyApp.Api.Tests.csproj\" />\n \u003c/Folder>\n\u003c/Solution>\n```\n\n**Key differences from .sln:**\n\n| Feature | .sln | .slnx |\n|---------|------|-------|\n| Format | Custom text | XML |\n| Readability | Low (GUIDs, custom syntax) | High (clean XML) |\n| Availability | All .NET versions | .NET 9+ preview |\n| Tooling | Full support | Partial (growing) |\n| Solution folders | Nested GUID references | `\u003cFolder>` elements |\n\n### When No Solution File Exists\n\nSome repositories use individual `.csproj` files without a `.sln`. Build and run from project directories:\n\n```bash\n# If no .sln exists, find all .csproj files and build individually\nfind . -name \"*.csproj\" -not -path \"*/obj/*\" | sort\ndotnet build src/MyApp.Api/MyApp.Api.csproj\n```\n\n\n## Subsection 3: Project Dependency Traversal\n\nUnderstanding `ProjectReference` chains is critical for determining build order, finding shared code, and identifying the impact of changes.\n\n### Discovery Commands\n\n```bash\n# Find all ProjectReference entries across the solution\ngrep -rn \"\u003cProjectReference\" --include=\"*.csproj\" . | grep -v \"obj/\"\n```\n\n**Example output:**\n\n```\nsrc/MyApp.Api/MyApp.Api.csproj:12: \u003cProjectReference Include=\"../MyApp.Core/MyApp.Core.csproj\" />\nsrc/MyApp.Api/MyApp.Api.csproj:13: \u003cProjectReference Include=\"../MyApp.Infrastructure/MyApp.Infrastructure.csproj\" />\nsrc/MyApp.Infrastructure/MyApp.Infrastructure.csproj:10: \u003cProjectReference Include=\"../MyApp.Core/MyApp.Core.csproj\" />\ntests/MyApp.Api.Tests/MyApp.Api.Tests.csproj:14: \u003cProjectReference Include=\"../../src/MyApp.Api/MyApp.Api.csproj\" />\n```\n\n### Building a Dependency Graph\n\nFrom the above output, the dependency graph is:\n\n```\nMyApp.Api.Tests\n -> MyApp.Api\n -> MyApp.Core\n -> MyApp.Infrastructure\n -> MyApp.Core\n```\n\n**Automated traversal using `dotnet list reference`:**\n\n```bash\n# List direct references for a specific project\ndotnet list src/MyApp.Api/MyApp.Api.csproj reference\n```\n\n**Example output:**\n\n```\nProject reference(s)\n--------------------\n../MyApp.Core/MyApp.Core.csproj\n../MyApp.Infrastructure/MyApp.Infrastructure.csproj\n```\n\n**Full transitive dependency analysis:**\n\n```bash\n# Build the full dependency tree by traversing transitively\n# Start from the top-level project and follow each reference\ndotnet list src/MyApp.Api/MyApp.Api.csproj reference\ndotnet list src/MyApp.Infrastructure/MyApp.Infrastructure.csproj reference\n# Continue until you reach projects with no ProjectReference entries\n```\n\n### Impact Analysis\n\nWhen modifying a shared project like `MyApp.Core`, all projects that reference it (directly or transitively) are affected:\n\n```bash\n# Find all projects that reference a specific project\ngrep -rn \"MyApp.Core.csproj\" --include=\"*.csproj\" . | grep -v \"obj/\"\n```\n\n**Example output:**\n\n```\nsrc/MyApp.Api/MyApp.Api.csproj:12: \u003cProjectReference Include=\"../MyApp.Core/MyApp.Core.csproj\" />\nsrc/MyApp.Infrastructure/MyApp.Infrastructure.csproj:10: \u003cProjectReference Include=\"../MyApp.Core/MyApp.Core.csproj\" />\ntests/MyApp.Core.Tests/MyApp.Core.Tests.csproj:14: \u003cProjectReference Include=\"../../src/MyApp.Core/MyApp.Core.csproj\" />\n```\n\nThis means changes to `MyApp.Core` require testing `MyApp.Api`, `MyApp.Infrastructure`, and `MyApp.Core.Tests`.\n\n\n## Subsection 4: Configuration File Locations\n\n.NET projects use several configuration files scattered across the solution. Knowing where to find them is essential for understanding application behavior.\n\n### appsettings*.json\n\n**Discovery command:**\n\n```bash\n# Find all appsettings files\nfind . -name \"appsettings*.json\" -not -path \"*/obj/*\" -not -path \"*/bin/*\" | sort\n```\n\n**Example output:**\n\n```\n./src/MyApp.Api/appsettings.json\n./src/MyApp.Api/appsettings.Development.json\n./src/MyApp.Api/appsettings.Production.json\n./src/MyApp.Worker/appsettings.json\n```\n\n**Key behavior:** Environment-specific files (`appsettings.{ENVIRONMENT}.json`) override values from the base `appsettings.json`. The environment is set via `DOTNET_ENVIRONMENT` or `ASPNETCORE_ENVIRONMENT`.\n\n### launchSettings.json\n\n**Discovery command:**\n\n```bash\n# Find launch settings (inside Properties/ folder of each project)\nfind . -name \"launchSettings.json\" -not -path \"*/obj/*\" -not -path \"*/bin/*\"\n```\n\n**Example output:**\n\n```\n./src/MyApp.Api/Properties/launchSettings.json\n./src/MyApp.Web/Properties/launchSettings.json\n```\n\n**Key behavior:** Used by `dotnet run` and Visual Studio to configure launch profiles (ports, environment variables, launch URLs). Not deployed to production.\n\n### Directory.Build.props and Directory.Build.targets\n\n**Discovery command:**\n\n```bash\n# Find all Directory.Build.props/targets files (may exist at multiple levels)\nfind . -name \"Directory.Build.props\" -o -name \"Directory.Build.targets\" | sort\n```\n\n**Example output:**\n\n```\n./Directory.Build.props\n./Directory.Build.targets\n./src/Directory.Build.props\n./tests/Directory.Build.props\n```\n\n**Key behavior:** MSBuild imports the nearest file found walking upward from the project directory. Nested files shadow parent files unless they explicitly import the parent (see `references/csproj-reading.md` for chaining).\n\n### Other Configuration Files\n\n```bash\n# Find all .NET configuration files in one sweep\nfind . \\( -name \"nuget.config\" -o -name \"global.json\" -o -name \".editorconfig\" \\\n -o -name \"Directory.Packages.props\" \\) -not -path \"*/obj/*\" | sort\n```\n\n**Example output:**\n\n```\n./.editorconfig\n./Directory.Packages.props\n./global.json\n./nuget.config\n./src/.editorconfig\n```\n\n| File | Purpose | Resolution |\n|------|---------|-----------|\n| `nuget.config` | NuGet package sources and mappings | Hierarchical upward from project dir |\n| `global.json` | SDK version pinning | Nearest file walking upward |\n| `.editorconfig` | Code style and analyzer severity | Hierarchical (sections merge upward) |\n| `Directory.Packages.props` | Central package version management | Hierarchical upward from project dir |\n\n\n## Subsection 5: Common Solution Layouts\n\nRecognizing the layout pattern helps agents navigate unfamiliar codebases faster.\n\n### Pattern 1: src/tests Layout\n\nThe most common layout. Source projects in `src/`, test projects in `tests/`, mirroring names.\n\n```\nMyApp/\n MyApp.sln\n Directory.Build.props\n Directory.Packages.props\n global.json\n nuget.config\n .editorconfig\n src/\n MyApp.Api/\n MyApp.Api.csproj\n Program.cs\n Controllers/\n Services/\n MyApp.Core/\n MyApp.Core.csproj\n Models/\n Interfaces/\n MyApp.Infrastructure/\n MyApp.Infrastructure.csproj\n Data/\n Repositories/\n tests/\n MyApp.Api.Tests/\n MyApp.Api.Tests.csproj\n MyApp.Core.Tests/\n MyApp.Core.Tests.csproj\n docs/\n architecture.md\n```\n\n**Heuristics:**\n- `src/` and `tests/` directories at root level.\n- Test project names mirror source project names with `.Tests` suffix.\n- Shared build config (`Directory.Build.props`, `global.json`) at the root.\n\n**Discovery:**\n\n```bash\n# Detect src/tests layout\nls -d src/ tests/ 2>/dev/null && echo \"src/tests layout detected\"\n```\n\n### Pattern 2: Vertical Slice Layout\n\nOrganizes code by feature rather than by technical layer. Each slice contains its own models, handlers, and endpoints.\n\n```\nMyApp/\n MyApp.sln\n src/\n MyApp.Api/\n MyApp.Api.csproj\n Program.cs\n Features/\n Orders/\n CreateOrder.cs # Handler + request + response\n GetOrder.cs\n OrderValidator.cs\n OrderEndpoints.cs # Minimal API endpoint mapping\n Products/\n CreateProduct.cs\n ListProducts.cs\n ProductEndpoints.cs\n Common/\n Behaviors/\n ValidationBehavior.cs\n Middleware/\n ExceptionMiddleware.cs\n tests/\n MyApp.Api.Tests/\n Features/\n Orders/\n CreateOrderTests.cs\n GetOrderTests.cs\n```\n\n**Heuristics:**\n- `Features/` directory within a project.\n- Each feature folder contains multiple related files (handler, validator, endpoint).\n- Tests mirror the feature folder structure.\n\n**Discovery:**\n\n```bash\n# Detect vertical slice layout\nfind . -type d -name \"Features\" -not -path \"*/obj/*\" -not -path \"*/bin/*\"\n```\n\n### Pattern 3: Modular Monolith\n\nMultiple bounded contexts as separate projects within a single solution, communicating through explicit interfaces or a shared message bus.\n\n```\nMyApp/\n MyApp.sln\n src/\n MyApp.Host/\n MyApp.Host.csproj # Composition root -- references all modules\n Program.cs\n Modules/\n Ordering/\n MyApp.Ordering/\n MyApp.Ordering.csproj\n OrderingModule.cs # Module registration (DI, endpoints)\n Domain/\n Application/\n Infrastructure/\n MyApp.Ordering.Tests/\n Catalog/\n MyApp.Catalog/\n MyApp.Catalog.csproj\n CatalogModule.cs\n Domain/\n Application/\n Infrastructure/\n MyApp.Catalog.Tests/\n MyApp.Shared/\n MyApp.Shared.csproj # Cross-cutting contracts (events, interfaces)\n```\n\n**Heuristics:**\n- `Modules/` directory with self-contained bounded contexts.\n- A `Host` or `Startup` project that references all modules.\n- A `Shared` project for cross-module contracts.\n\n**Discovery:**\n\n```bash\n# Detect modular monolith layout\nfind . -type d -name \"Modules\" -not -path \"*/obj/*\" -not -path \"*/bin/*\"\n# Or look for module registration patterns\ngrep -rn \"Module\\|AddModule\\|RegisterModule\" --include=\"*.cs\" . | grep -v \"obj/\" | head -10\n```\n\n\n## Slopwatch Anti-Patterns\n\nThese patterns in test project discovery indicate an agent is hiding testing gaps rather than addressing them. See [skill:dotnet-testing] for the automated quality gate that detects these patterns.\n\n### Disabled or Skipped Tests in Test Project Discovery\n\nWhen navigating a solution and identifying test projects, watch for tests that exist but are silently disabled:\n\n```csharp\n// RED FLAG: skipped tests that will not run during dotnet test\n[Fact(Skip = \"Flaky -- revisit later\")]\npublic async Task ProcessOrder_ConcurrentRequests_HandledCorrectly() { }\n\n// RED FLAG: entire test class disabled via conditional compilation\n#if false\npublic class OrderIntegrationTests\n{\n [Fact]\n public async Task CreateOrder_PersistsToDatabase() { }\n}\n#endif\n\n// RED FLAG: commented-out test methods\n// [Fact]\n// public void CalculateDiscount_NegativeAmount_ThrowsException() { }\n```\n\n**Discovery commands to check for disabled tests:**\n\n```bash\n# Find skipped tests\ngrep -rEn 'Skip[[:space:]]*=' --include=\"*.cs\" . | grep -v \"obj/\" | grep -v \"bin/\"\n\n# Find tests hidden behind #if false\ngrep -rn \"#if false\" --include=\"*.cs\" . | grep -v \"obj/\" | grep -v \"bin/\"\n\n# Find commented-out test attributes\ngrep -rEn '//[[:space:]]*\\[(Fact|Theory|Test)\\]' --include=\"*.cs\" . | grep -v \"obj/\" | grep -v \"bin/\"\n```\n\n**Fix:** Investigate why tests are disabled. If they are flaky due to timing, fix the non-determinism or use `[Retry]` (xUnit v3). If they test removed functionality, delete them. Never leave disabled tests as invisible technical debt.\n\n\n## Cross-References\n\n- `references/project-structure.md` -- project organization, SDK selection, solution layout decisions\n- `references/csproj-reading.md` -- reading and modifying .csproj files found during navigation\n- [skill:dotnet-testing] -- test project identification, test types, test organization\n\n## References\n\n- [.NET Project SDK Overview](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview)\n- [dotnet sln Command](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-sln)\n- [.slnx Solution Format](https://learn.microsoft.com/en-us/visualstudio/ide/reference/solution-file-slnx)\n- [Configuration in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/)\n- [Directory.Build.props/targets](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":18179,"content_sha256":"990fa9dfd091bbc75ee5a972c9764c916a66caf2122b88d1ee43d7f6a3aea983"},{"filename":"references/spectre-console.md","content":"# Spectre.Console\n\nSpectre.Console for building rich console output (tables, trees, progress bars, prompts, markup, live displays) and Spectre.Console.Cli for structured command-line application parsing. Cross-platform across Windows, macOS, and Linux terminals.\n\n**Version assumptions:** .NET 8.0+ baseline. Spectre.Console 0.54.0 (latest stable). Spectre.Console.Cli 0.53.1 (latest stable). Both packages target net8.0+ and netstandard2.0.\n\n## Package References\n\n```xml\n\u003cItemGroup>\n \u003c!-- Rich console output: markup, tables, trees, progress, prompts, live displays -->\n \u003cPackageReference Include=\"Spectre.Console\" Version=\"0.54.0\" />\n\n \u003c!-- CLI command framework (adds command parsing, settings, DI support) -->\n \u003cPackageReference Include=\"Spectre.Console.Cli\" Version=\"0.53.1\" />\n\u003c/ItemGroup>\n```\n\nSpectre.Console.Cli has a dependency on Spectre.Console -- install both only when you need the CLI framework. For rich output only, Spectre.Console alone is sufficient.\n\n\n## Markup and Styling\n\nSpectre.Console uses a BBCode-inspired markup syntax for styled console output.\n\n### Basic Markup\n\n```csharp\nusing Spectre.Console;\n\n// Styled text with markup tags\nAnsiConsole.MarkupLine(\"[bold red]Error:[/] File not found.\");\nAnsiConsole.MarkupLine(\"[green]Success![/] Build completed in [blue]2.3s[/].\");\nAnsiConsole.MarkupLine(\"[underline]https://example.com[/]\");\nAnsiConsole.MarkupLine(\"[dim italic]This is subtle text[/]\");\n\n// Nested styles\nAnsiConsole.MarkupLine(\"[bold [red on white]Warning:[/] check config[/]\");\n\n// Escape brackets with double brackets\nAnsiConsole.MarkupLine(\"Use [[bold]] for bold text.\");\n```\n\n### Figlet Text\n\n```csharp\nAnsiConsole.Write(\n new FigletText(\"Hello!\")\n .Color(Color.Green)\n .Centered());\n```\n\n### Rule (Horizontal Line)\n\n```csharp\n// Simple rule\nAnsiConsole.Write(new Rule());\n\n// Titled rule\nAnsiConsole.Write(new Rule(\"[yellow]Section Title[/]\"));\n\n// Aligned rule\nAnsiConsole.Write(new Rule(\"[blue]Left Aligned[/]\").LeftJustified());\n```\n\n\n## Tables\n\n```csharp\nvar table = new Table();\n\n// Add columns\ntable.AddColumn(\"Name\");\ntable.AddColumn(new TableColumn(\"Age\").Centered());\ntable.AddColumn(new TableColumn(\"City\").RightAligned());\n\n// Add rows\ntable.AddRow(\"Alice\", \"30\", \"Seattle\");\ntable.AddRow(\"[green]Bob[/]\", \"25\", \"Portland\");\ntable.AddRow(\"Charlie\", \"35\", \"Vancouver\");\n\n// Styling\ntable.Border(TableBorder.Rounded);\ntable.BorderColor(Color.Grey);\ntable.Title(\"[underline]Team Members[/]\");\ntable.Caption(\"[dim]Updated daily[/]\");\n\n// Column configuration\ntable.Columns[0].PadLeft(2);\ntable.Columns[0].NoWrap();\n\nAnsiConsole.Write(table);\n```\n\n### Nested Tables\n\n```csharp\nvar innerTable = new Table()\n .AddColumn(\"Detail\")\n .AddColumn(\"Value\")\n .AddRow(\"Role\", \"Developer\")\n .AddRow(\"Level\", \"Senior\");\n\nvar outerTable = new Table()\n .AddColumn(\"Name\")\n .AddColumn(\"Info\")\n .AddRow(\"Alice\", innerTable);\n\nAnsiConsole.Write(outerTable);\n```\n\n\n## Trees\n\n```csharp\nvar tree = new Tree(\"Solution\");\n\n// Add nodes\nvar srcNode = tree.AddNode(\"[yellow]src[/]\");\nvar apiNode = srcNode.AddNode(\"Api\");\napiNode.AddNode(\"Controllers/\");\napiNode.AddNode(\"Program.cs\");\n\nvar libNode = srcNode.AddNode(\"Library\");\nlibNode.AddNode(\"Services/\");\n\nvar testNode = tree.AddNode(\"[blue]tests[/]\");\ntestNode.AddNode(\"Api.Tests/\");\n\n// Styling\ntree.Style = Style.Parse(\"dim\");\n\nAnsiConsole.Write(tree);\n```\n\n\n## Panels\n\n```csharp\nvar panel = new Panel(\"This is [green]important[/] content.\")\n .Header(\"[bold]Notice[/]\")\n .Border(BoxBorder.Rounded)\n .BorderColor(Color.Blue)\n .Padding(2, 1) // horizontal, vertical\n .Expand(); // fill available width\n\nAnsiConsole.Write(panel);\n```\n\n### Composing Renderables with Columns\n\n```csharp\nAnsiConsole.Write(new Columns(\n new Panel(\"Left panel\").Expand(),\n new Panel(\"Right panel\").Expand()));\n```\n\n\n## Progress Displays\n\n### Progress Bars\n\n```csharp\nawait AnsiConsole.Progress()\n .AutoClear(false) // keep completed tasks visible\n .HideCompleted(false)\n .Columns(\n new TaskDescriptionColumn(),\n new ProgressBarColumn(),\n new PercentageColumn(),\n new RemainingTimeColumn(),\n new SpinnerColumn())\n .StartAsync(async ctx =>\n {\n var downloadTask = ctx.AddTask(\"[green]Downloading[/]\", maxValue: 100);\n var extractTask = ctx.AddTask(\"[blue]Extracting[/]\", maxValue: 100);\n\n while (!ctx.IsFinished)\n {\n await Task.Delay(50);\n downloadTask.Increment(1.5);\n\n if (downloadTask.Value > 50)\n {\n extractTask.Increment(0.8);\n }\n }\n });\n```\n\n### Status Spinners\n\n```csharp\nawait AnsiConsole.Status()\n .Spinner(Spinner.Known.Dots)\n .SpinnerStyle(Style.Parse(\"green bold\"))\n .StartAsync(\"Processing...\", async ctx =>\n {\n await Task.Delay(1000);\n ctx.Status(\"Compiling...\");\n ctx.Spinner(Spinner.Known.Star);\n await Task.Delay(1000);\n ctx.Status(\"Publishing...\");\n await Task.Delay(1000);\n });\n```\n\n\n## Prompts\n\n### Text Prompt\n\n```csharp\n// Simple typed input\nvar name = AnsiConsole.Ask\u003cstring>(\"What's your [green]name[/]?\");\nvar age = AnsiConsole.Ask\u003cint>(\"What's your [green]age[/]?\");\n\n// With default value\nvar city = AnsiConsole.Prompt(\n new TextPrompt\u003cstring>(\"Enter [green]city[/]:\")\n .DefaultValue(\"Seattle\")\n .ShowDefaultValue());\n\n// Secret input (password)\nvar password = AnsiConsole.Prompt(\n new TextPrompt\u003cstring>(\"Enter [green]password[/]:\")\n .Secret());\n\n// With validation\nvar email = AnsiConsole.Prompt(\n new TextPrompt\u003cstring>(\"Enter [green]email[/]:\")\n .Validate(input =>\n input.Contains('@') && input.Contains('.')\n ? ValidationResult.Success()\n : ValidationResult.Error(\"[red]Invalid email address[/]\")));\n\n// Optional (allow empty)\nvar nickname = AnsiConsole.Prompt(\n new TextPrompt\u003cstring>(\"Enter [green]nickname[/] (optional):\")\n .AllowEmpty());\n```\n\n### Confirmation Prompt\n\n```csharp\nbool proceed = AnsiConsole.Confirm(\"Continue with deployment?\");\n```\n\n### Selection Prompt\n\n```csharp\nvar fruit = AnsiConsole.Prompt(\n new SelectionPrompt\u003cstring>()\n .Title(\"Pick a [green]fruit[/]:\")\n .PageSize(10)\n .EnableSearch()\n .WrapAround()\n .AddChoices(\"Apple\", \"Banana\", \"Orange\", \"Mango\", \"Grape\"));\n\n// Grouped choices\nvar country = AnsiConsole.Prompt(\n new SelectionPrompt\u003cstring>()\n .Title(\"Select [green]destination[/]:\")\n .AddChoiceGroup(\"Europe\", \"France\", \"Italy\", \"Spain\")\n .AddChoiceGroup(\"Asia\", \"Japan\", \"Thailand\", \"Vietnam\"));\n```\n\n### Multi-Selection Prompt\n\n```csharp\nvar toppings = AnsiConsole.Prompt(\n new MultiSelectionPrompt\u003cstring>()\n .Title(\"Choose [green]toppings[/]:\")\n .PageSize(10)\n .Required()\n .InstructionsText(\"[grey](Press [blue]\u003cspace>[/] to toggle, [green]\u003center>[/] to accept)[/]\")\n .AddChoices(\"Cheese\", \"Pepperoni\", \"Mushrooms\", \"Olives\", \"Onions\"));\n```\n\n\n## Live Displays\n\nLive displays update in-place for dynamic content that changes over time.\n\n```csharp\nvar table = new Table()\n .AddColumn(\"Time\")\n .AddColumn(\"Status\");\n\nawait AnsiConsole.Live(table)\n .AutoClear(false)\n .Overflow(VerticalOverflow.Ellipsis)\n .Cropping(VerticalOverflowCropping.Bottom)\n .StartAsync(async ctx =>\n {\n table.AddRow(DateTime.Now.ToString(\"T\"), \"[yellow]Starting...[/]\");\n ctx.Refresh();\n await Task.Delay(1000);\n\n table.AddRow(DateTime.Now.ToString(\"T\"), \"[green]Processing...[/]\");\n ctx.Refresh();\n await Task.Delay(1000);\n\n table.AddRow(DateTime.Now.ToString(\"T\"), \"[blue]Complete![/]\");\n ctx.Refresh();\n });\n```\n\n### Replacing the Target\n\n```csharp\nawait AnsiConsole.Live(new Markup(\"[yellow]Initializing...[/]\"))\n .StartAsync(async ctx =>\n {\n await Task.Delay(1000);\n ctx.UpdateTarget(new Markup(\"[green]Ready![/]\"));\n await Task.Delay(1000);\n ctx.UpdateTarget(\n new Panel(\"Final result: [bold]42[/]\")\n .Header(\"Done\")\n .Border(BoxBorder.Rounded));\n });\n```\n\n\n## Spectre.Console.Cli Framework\n\nSpectre.Console.Cli provides a structured command-line parsing framework with command hierarchies, typed settings, validation, and automatic help generation.\n\n### Basic Command App\n\n```csharp\nusing Spectre.Console.Cli;\n\nvar app = new CommandApp\u003cGreetCommand>();\nreturn app.Run(args);\n\n// Command with typed settings\npublic sealed class GreetSettings : CommandSettings\n{\n [CommandArgument(0, \"\u003cname>\")]\n [Description(\"The person to greet\")]\n public string Name { get; init; } = string.Empty;\n\n [CommandOption(\"-c|--count\")]\n [Description(\"Number of times to greet\")]\n [DefaultValue(1)]\n public int Count { get; init; }\n\n [CommandOption(\"--shout\")]\n [Description(\"Greet in uppercase\")]\n public bool Shout { get; init; }\n}\n\npublic sealed class GreetCommand : Command\u003cGreetSettings>\n{\n public override int Execute(CommandContext context, GreetSettings settings)\n {\n for (int i = 0; i \u003c settings.Count; i++)\n {\n var greeting = $\"Hello, {settings.Name}!\";\n AnsiConsole.MarkupLine(settings.Shout\n ? $\"[bold]{greeting.ToUpperInvariant()}[/]\"\n : greeting);\n }\n return 0; // exit code\n }\n}\n```\n\n### Command Hierarchy with Branches\n\n```csharp\nvar app = new CommandApp();\napp.Configure(config =>\n{\n config.AddBranch\u003cRemoteSettings>(\"remote\", remote =>\n {\n remote.AddCommand\u003cRemoteAddCommand>(\"add\")\n .WithDescription(\"Add a remote\");\n remote.AddCommand\u003cRemoteRemoveCommand>(\"remove\")\n .WithDescription(\"Remove a remote\");\n });\n\n config.AddCommand\u003cCloneCommand>(\"clone\")\n .WithDescription(\"Clone a repository\");\n});\n\nreturn app.Run(args);\n\n// Shared settings for the branch -- inherited by subcommands\npublic class RemoteSettings : CommandSettings\n{\n [CommandOption(\"-v|--verbose\")]\n [Description(\"Verbose output\")]\n public bool Verbose { get; init; }\n}\n\n// Subcommand settings inherit from branch settings\npublic sealed class RemoteAddSettings : RemoteSettings\n{\n [CommandArgument(0, \"\u003cname>\")]\n public string Name { get; init; } = string.Empty;\n\n [CommandArgument(1, \"\u003curl>\")]\n public string Url { get; init; } = string.Empty;\n}\n\npublic sealed class RemoteAddCommand : Command\u003cRemoteAddSettings>\n{\n public override int Execute(CommandContext context, RemoteAddSettings settings)\n {\n if (settings.Verbose)\n {\n AnsiConsole.MarkupLine($\"[dim]Adding remote...[/]\");\n }\n AnsiConsole.MarkupLine($\"Added remote [green]{settings.Name}[/] -> {settings.Url}\");\n return 0;\n }\n}\n```\n\n### Settings Validation\n\n```csharp\npublic sealed class DeploySettings : CommandSettings\n{\n [CommandArgument(0, \"\u003cenvironment>\")]\n public string Environment { get; init; } = string.Empty;\n\n [CommandOption(\"--timeout\")]\n [DefaultValue(30)]\n public int TimeoutSeconds { get; init; }\n\n public override ValidationResult Validate()\n {\n var validEnvs = new[] { \"dev\", \"staging\", \"prod\" };\n if (!validEnvs.Contains(Environment, StringComparer.OrdinalIgnoreCase))\n {\n return ValidationResult.Error(\n $\"Environment must be one of: {string.Join(\", \", validEnvs)}\");\n }\n\n if (TimeoutSeconds \u003c= 0)\n {\n return ValidationResult.Error(\"Timeout must be positive\");\n }\n\n return ValidationResult.Success();\n }\n}\n```\n\n### Async Commands\n\n```csharp\npublic sealed class FetchCommand : AsyncCommand\u003cFetchSettings>\n{\n public override async Task\u003cint> ExecuteAsync(\n CommandContext context, FetchSettings settings)\n {\n await AnsiConsole.Status()\n .StartAsync(\"Fetching data...\", async ctx =>\n {\n await Task.Delay(2000); // simulate work\n });\n\n AnsiConsole.MarkupLine(\"[green]Done![/]\");\n return 0;\n }\n}\n```\n\n### Dependency Injection with ITypeRegistrar\n\n```csharp\nusing Microsoft.Extensions.DependencyInjection;\nusing Spectre.Console.Cli;\n\n// Set up DI container\nvar services = new ServiceCollection();\nservices.AddSingleton\u003cIGreetingService, GreetingService>();\nservices.AddSingleton\u003cIAnsiConsole>(AnsiConsole.Console);\n\nvar registrar = new TypeRegistrar(services);\nvar app = new CommandApp\u003cGreetCommand>(registrar);\nreturn app.Run(args);\n\n// TypeRegistrar bridges Microsoft DI to Spectre.Console.Cli\npublic sealed class TypeRegistrar(IServiceCollection services) : ITypeRegistrar\n{\n public ITypeResolver Build() => new TypeResolver(services.BuildServiceProvider());\n\n public void Register(Type service, Type implementation)\n => services.AddSingleton(service, implementation);\n\n public void RegisterInstance(Type service, object implementation)\n => services.AddSingleton(service, implementation);\n\n public void RegisterLazy(Type service, Func\u003cobject> factory)\n => services.AddSingleton(service, _ => factory());\n}\n\npublic sealed class TypeResolver(IServiceProvider provider) : ITypeResolver\n{\n public object? Resolve(Type? type)\n => type is null ? null : provider.GetService(type);\n}\n\n// Command receives services via constructor injection\npublic sealed class GreetCommand(IGreetingService greetingService) : Command\u003cGreetSettings>\n{\n public override int Execute(CommandContext context, GreetSettings settings)\n {\n var message = greetingService.GetGreeting(settings.Name);\n AnsiConsole.MarkupLine(message);\n return 0;\n }\n}\n```\n\n\n## Testable Console Output\n\nSpectre.Console provides `IAnsiConsole` for testable output instead of writing directly to the real console.\n\n```csharp\n// Production: use AnsiConsole.Console (the real console)\nIAnsiConsole console = AnsiConsole.Console;\n\n// Testing: use a recording console\nvar console = AnsiConsole.Create(new AnsiConsoleSettings\n{\n Out = new AnsiConsoleOutput(new StringWriter())\n});\n\n// Use the abstraction instead of static AnsiConsole methods\nconsole.MarkupLine(\"[green]Testable output[/]\");\nconsole.Write(new Table().AddColumn(\"Col\").AddRow(\"Val\"));\n```\n\n\n## Agent Gotchas\n\n1. **Do not use `AnsiConsole.Markup*` in redirected output.** When stdout is redirected (piped to a file or another process), ANSI escape codes corrupt the output. Check `AnsiConsole.Profile.Capabilities.Ansi` before using markup, or use `IAnsiConsole` with appropriate settings. See [skill:dotnet-csharp] for async pipeline patterns.\n2. **Do not assume ANSI support in CI environments.** CI runners (GitHub Actions, Azure Pipelines) may not support ANSI escape codes. Set `TERM=dumb` or use `AnsiConsole.Create()` with `ColorSystemSupport.NoColors` for CI-safe output. Spectre.Console auto-detects capabilities, but explicit configuration prevents flaky rendering.\n3. **Do not mix `AnsiConsole` static calls with `IAnsiConsole` instance calls.** Static `AnsiConsole.Write()` always targets the real console. When using DI with `IAnsiConsole`, consistently use the injected instance. Mixing the two produces duplicated or interleaved output.\n4. **Do not modify a renderable from a background thread during `Live()`.** Live displays are not thread-safe. Mutate the target renderable only inside the `Start`/`StartAsync` callback, then call `ctx.Refresh()`. Concurrent mutations cause corrupted terminal output.\n5. **Do not use prompts in non-interactive contexts.** `TextPrompt`, `SelectionPrompt`, and `ConfirmationPrompt` block waiting for user input. In CI or automated scripts, use environment variables or command-line arguments for input instead of prompts. Check `AnsiConsole.Profile.Capabilities.Interactive` before prompting.\n6. **Do not confuse Spectre.Console.Cli with System.CommandLine.** They are independent frameworks with different APIs. Spectre.Console.Cli uses `CommandSettings` classes with `[CommandArgument]`/`[CommandOption]` attributes, while System.CommandLine uses `Option\u003cT>` and `Argument\u003cT>` builder pattern. Do not mix APIs. For System.CommandLine, see `references/system-commandline.md`.\n7. **Do not forget `ctx.Refresh()` after modifying live display content.** Changes to tables, trees, or panels inside a `Live()` callback are not rendered until `ctx.Refresh()` is called. Omitting it produces stale displays.\n8. **Do not hardcode color values without fallback.** Terminals with limited color support silently degrade TrueColor values. Use named colors (`Color.Green`) when possible and test with `NO_COLOR=1` environment variable to verify graceful degradation.\n\n\n## Prerequisites\n\n- **NuGet packages:** `Spectre.Console` 0.54.0 for rich output; add `Spectre.Console.Cli` 0.53.1 for CLI framework\n- **Target framework:** net8.0 or later (also supports netstandard2.0)\n- **Terminal:** Any terminal emulator supporting ANSI escape sequences. Windows Terminal, iTerm2, or modern Linux terminal recommended for best experience (TrueColor, Unicode). Console output degrades gracefully on limited terminals.\n- **For DI with Spectre.Console.Cli:** `Microsoft.Extensions.DependencyInjection` package for the `ITypeRegistrar`/`ITypeResolver` bridge\n\n\n## References\n\n- [Spectre.Console GitHub](https://github.com/spectreconsole/spectre.console) -- source code, issues, samples\n- [Spectre.Console Documentation](https://spectreconsole.net/) -- official guides and API reference\n- [Spectre.Console NuGet](https://www.nuget.org/packages/Spectre.Console) -- package downloads and version history\n- [Spectre.Console.Cli NuGet](https://www.nuget.org/packages/Spectre.Console.Cli) -- CLI framework package\n- [Spectre.Console Examples](https://github.com/spectreconsole/spectre.console/tree/main/examples) -- official example projects\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":17880,"content_sha256":"078f8d7c0ae19115c5e01d229c15739c6e9cce6d3b88db569c8fec7b6313074c"},{"filename":"references/system-commandline.md","content":"# System.CommandLine\n\nSystem.CommandLine 2.0 stable API for building .NET CLI applications. Covers RootCommand, Command, Option\\\u003cT\\>, Argument\\\u003cT\\>, SetAction for handler binding, ParseResult-based value access, custom type parsing, validation, tab completion, and testing with TextWriter capture.\n\n**Version assumptions:** .NET 8.0+ baseline. System.CommandLine 2.0.0+ (stable NuGet package, GA since November 2025). All examples target the 2.0.0 GA API surface.\n\n**Breaking change note:** System.CommandLine 2.0.0 GA differs significantly from the pre-release beta4 API. Key changes: `SetHandler` replaced by `SetAction`, `ICommandHandler` removed in favor of `SynchronousCommandLineAction`/`AsynchronousCommandLineAction`, `InvocationContext` removed (ParseResult passed directly), `CommandLineBuilder` and `AddMiddleware` removed, `IConsole` removed in favor of TextWriter properties, and the `System.CommandLine.Hosting`/`System.CommandLine.NamingConventionBinder` packages discontinued. Do not use beta-era patterns.\n\n## Package Reference\n\n```xml\n\u003cItemGroup>\n \u003cPackageReference Include=\"System.CommandLine\" Version=\"2.0.*\" />\n\u003c/ItemGroup>\n```\n\nSystem.CommandLine 2.0 targets .NET 8+ and .NET Standard 2.0. A single package provides all functionality -- the separate `System.CommandLine.Hosting`, `System.CommandLine.NamingConventionBinder`, and `System.CommandLine.Rendering` packages from the beta era are discontinued.\n\n\n## RootCommand and Command Hierarchy\n\n### Basic Command Structure\n\n```csharp\nusing System.CommandLine;\n\n// Root command -- the entry point\nvar rootCommand = new RootCommand(\"My CLI tool description\");\n\n// Add a subcommand via mutable collection\nvar listCommand = new Command(\"list\", \"List all items\");\nrootCommand.Subcommands.Add(listCommand);\n\n// Nested subcommands: mycli migrate up\nvar migrateCommand = new Command(\"migrate\", \"Database migrations\");\nvar upCommand = new Command(\"up\", \"Apply pending migrations\");\nvar downCommand = new Command(\"down\", \"Revert last migration\");\nmigrateCommand.Subcommands.Add(upCommand);\nmigrateCommand.Subcommands.Add(downCommand);\nrootCommand.Subcommands.Add(migrateCommand);\n```\n\n### Collection Initializer Syntax\n\n```csharp\n// Fluent collection initializer (commands, options, arguments)\nRootCommand rootCommand = new(\"My CLI tool\")\n{\n new Option\u003cstring>(\"--output\", \"-o\") { Description = \"Output file path\" },\n new Argument\u003cFileInfo>(\"file\") { Description = \"Input file\" },\n new Command(\"list\", \"List all items\")\n {\n new Option\u003cint>(\"--limit\") { Description = \"Max items to return\" }\n }\n};\n```\n\n\n## Options and Arguments\n\n### Option\\\u003cT\\> -- Named Parameters\n\n```csharp\n// Option\u003cT> -- named parameter (--output, -o)\n// name is the first parameter; additional params are aliases\nvar outputOption = new Option\u003cFileInfo>(\"--output\", \"-o\")\n{\n Description = \"Output file path\",\n Required = true // was IsRequired in beta4\n};\n\n// Option with default value via DefaultValueFactory\nvar verbosityOption = new Option\u003cint>(\"--verbosity\")\n{\n Description = \"Verbosity level (0-3)\",\n DefaultValueFactory = _ => 1\n};\n```\n\n### Argument\\\u003cT\\> -- Positional Parameters\n\n```csharp\n// Argument\u003cT> -- positional parameter\n// name is mandatory in 2.0 (used for help text)\nvar fileArgument = new Argument\u003cFileInfo>(\"file\")\n{\n Description = \"Input file to process\"\n};\n\nrootCommand.Arguments.Add(fileArgument);\n```\n\n### Constrained Values\n\n```csharp\nvar formatOption = new Option\u003cstring>(\"--format\")\n{\n Description = \"Output format\"\n};\nformatOption.AcceptOnlyFromAmong(\"json\", \"csv\", \"table\");\n\nrootCommand.Options.Add(formatOption);\n```\n\n### Aliases\n\n```csharp\n// Aliases are separate from the name in 2.0\n// First constructor param is the name; rest are aliases\nvar verboseOption = new Option\u003cbool>(\"--verbose\", \"-v\")\n{\n Description = \"Enable verbose output\"\n};\n\n// Or add aliases after construction\nverboseOption.Aliases.Add(\"-V\");\n```\n\n### Global Options\n\n```csharp\n// Global options are inherited by all subcommands\nvar debugOption = new Option\u003cbool>(\"--debug\")\n{\n Description = \"Enable debug mode\",\n Recursive = true // makes it global (inherited by subcommands)\n};\nrootCommand.Options.Add(debugOption);\n```\n\n\n## Setting Actions (Command Handlers)\n\nIn 2.0.0 GA, `SetHandler` is replaced by `SetAction`. Actions receive a `ParseResult` directly (no `InvocationContext`).\n\n### Synchronous Action\n\n```csharp\nvar outputOption = new Option\u003cFileInfo>(\"--output\", \"-o\")\n{\n Description = \"Output file path\",\n Required = true\n};\nvar verbosityOption = new Option\u003cint>(\"--verbosity\")\n{\n DefaultValueFactory = _ => 1\n};\n\nrootCommand.Options.Add(outputOption);\nrootCommand.Options.Add(verbosityOption);\n\nrootCommand.SetAction(parseResult =>\n{\n var output = parseResult.GetValue(outputOption)!;\n var verbosity = parseResult.GetValue(verbosityOption);\n Console.WriteLine($\"Output: {output.FullName}, Verbosity: {verbosity}\");\n return 0; // exit code\n});\n```\n\n### Asynchronous Action with CancellationToken\n\n```csharp\n// Async actions receive ParseResult AND CancellationToken\nrootCommand.SetAction(async (ParseResult parseResult, CancellationToken ct) =>\n{\n var output = parseResult.GetValue(outputOption)!;\n var verbosity = parseResult.GetValue(verbosityOption);\n await ProcessAsync(output, verbosity, ct);\n return 0;\n});\n```\n\n### Getting Values by Name\n\n```csharp\n// Values can also be retrieved by symbol name (requires type parameter)\nrootCommand.SetAction(parseResult =>\n{\n int delay = parseResult.GetValue\u003cint>(\"--delay\");\n string? message = parseResult.GetValue\u003cstring>(\"--message\");\n Console.WriteLine($\"Delay: {delay}, Message: {message}\");\n});\n```\n\n### Parsing and Invoking\n\n```csharp\n// Program.cs entry point -- parse then invoke\nstatic int Main(string[] args)\n{\n var rootCommand = BuildCommand();\n ParseResult parseResult = rootCommand.Parse(args);\n return parseResult.Invoke();\n}\n\n// Async entry point\nstatic async Task\u003cint> Main(string[] args)\n{\n var rootCommand = BuildCommand();\n ParseResult parseResult = rootCommand.Parse(args);\n return await parseResult.InvokeAsync();\n}\n```\n\n### Parse Without Invoking\n\n```csharp\n// Parse-only mode: inspect results without running actions\nParseResult parseResult = rootCommand.Parse(args);\nif (parseResult.Errors.Count > 0)\n{\n foreach (var error in parseResult.Errors)\n {\n Console.Error.WriteLine(error.Message);\n }\n return 1;\n}\n\nFileInfo? file = parseResult.GetValue(fileOption);\n// Process directly without SetAction\n```\n\n\nFor detailed examples (custom parsing, validation, configuration, tab completion, DI, testing, migration), see the Detailed Examples section below.\n\n## Agent Gotchas\n\n1. **Do not use beta4 API patterns.** The 2.0.0 GA API is fundamentally different. There is no `SetHandler` -- use `SetAction`. There is no `InvocationContext` -- actions receive `ParseResult` directly. There is no `CommandLineBuilder` -- configuration uses `ParserConfiguration`/`InvocationConfiguration`.\n2. **Do not reference discontinued packages.** `System.CommandLine.Hosting`, `System.CommandLine.NamingConventionBinder`, and `System.CommandLine.Rendering` are discontinued. Use the single `System.CommandLine` package.\n3. **Do not confuse `Option\u003cT>` with `Argument\u003cT>`.** Options are named (`--output file.txt`), arguments are positional (`mycli file.txt`). Using the wrong type produces confusing parse errors.\n4. **Do not use `AddOption`/`AddCommand`/`AddAlias` methods.** These were replaced by mutable collection properties: `Options.Add`, `Subcommands.Add`, `Aliases.Add`. The old methods do not exist in 2.0.0.\n5. **Do not use `IConsole` or `TestConsole` for testing.** These interfaces were removed. Use `InvocationConfiguration` with `StringWriter` for `Output`/`Error` to capture test output.\n6. **Do not ignore the `CancellationToken` in async actions.** In 2.0.0 GA, `CancellationToken` is a mandatory second parameter for async `SetAction` delegates. The compiler warns (CA2016) when it is not propagated.\n7. **Do not write `Console.Out` directly in command actions.** Write to `InvocationConfiguration.Output` for testability. If no configuration is provided, output goes to `Console.Out` by default, but direct writes bypass test capture.\n8. **Do not set default values via constructors.** Use the `DefaultValueFactory` property instead. The old `getDefaultValue` constructor parameter does not exist in 2.0.0.\n\n\n## References\n\n- [System.CommandLine overview](https://learn.microsoft.com/en-us/dotnet/standard/commandline/)\n- [System.CommandLine migration guide (beta5+)](https://learn.microsoft.com/en-us/dotnet/standard/commandline/migration-guide-2.0.0-beta5)\n- [How to parse and invoke](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-parse-and-invoke)\n- [How to customize parsing and validation](https://learn.microsoft.com/en-us/dotnet/standard/commandline/how-to-customize-parsing-and-validation)\n- [System.CommandLine GitHub](https://github.com/dotnet/command-line-api)\n\n\n## Attribution\n\nAdapted from [Aaronontheweb/dotnet-skills](https://github.com/Aaronontheweb/dotnet-skills) (MIT license).\n\n---\n\n# System.CommandLine -- Detailed Examples\n\nExtended code examples for custom type parsing, validation, configuration, tab completion, DI integration, testing, response files, and migration from beta4.\n\n---\n\n## Custom Type Parsing\n\n### CustomParser Property\n\nFor types without built-in parsers, use the `CustomParser` property on `Option\u003cT>` or `Argument\u003cT>`.\n\n```csharp\npublic record ConnectionInfo(string Host, int Port);\n\nvar connectionOption = new Option\u003cConnectionInfo?>(\"--connection\")\n{\n Description = \"Connection as host:port\",\n CustomParser = result =>\n {\n var raw = result.Tokens.SingleOrDefault()?.Value;\n if (raw is null)\n {\n result.AddError(\"--connection requires a value\");\n return null;\n }\n\n var parts = raw.Split(':');\n if (parts.Length != 2 || !int.TryParse(parts[1], out var port))\n {\n result.AddError(\"Expected format: host:port\");\n return null;\n }\n\n return new ConnectionInfo(parts[0], port);\n }\n};\n```\n\n### DefaultValueFactory\n\n```csharp\nvar portOption = new Option\u003cint>(\"--port\")\n{\n Description = \"Server port\",\n DefaultValueFactory = _ => 8080 // type-safe default\n};\n```\n\n### Combining CustomParser with Validation\n\n```csharp\nvar uriOption = new Option\u003cUri?>(\"--uri\")\n{\n Description = \"Target URI\",\n CustomParser = result =>\n {\n var raw = result.Tokens.SingleOrDefault()?.Value;\n if (raw is null) return null;\n\n if (!Uri.TryCreate(raw, UriKind.Absolute, out var uri))\n {\n result.AddError(\"Invalid URI format\");\n return null;\n }\n\n if (uri.Scheme != \"https\")\n {\n result.AddError(\"Only HTTPS URIs are accepted\");\n return null;\n }\n\n return uri;\n }\n};\n```\n\n---\n\n## Validation\n\n### Option and Argument Validators\n\n```csharp\n// Validators use Validators.Add (not AddValidator in 2.0)\nvar portOption = new Option\u003cint>(\"--port\") { Description = \"Port number\" };\nportOption.Validators.Add(result =>\n{\n var value = result.GetValue(portOption);\n if (value \u003c 1 || value > 65535)\n {\n result.AddError(\"Port must be between 1 and 65535\");\n }\n});\n\n// Arity constraints\nvar tagsOption = new Option\u003cstring[]>(\"--tag\")\n{\n Arity = new ArgumentArity(1, 5), // 1 to 5 tags\n AllowMultipleArgumentsPerToken = true\n};\n```\n\n### Built-In Validators\n\n```csharp\n// Accept only existing files/directories\nvar inputOption = new Option\u003cFileInfo>(\"--input\");\ninputOption.AcceptExistingOnly();\n\n// Accept only legal file names\nvar nameArg = new Argument\u003cstring>(\"name\");\nnameArg.AcceptLegalFileNamesOnly();\n\n// Accept only from a set of values (moved from FromAmong)\nvar envOption = new Option\u003cstring>(\"--env\");\nenvOption.AcceptOnlyFromAmong(\"dev\", \"staging\", \"prod\");\n```\n\n---\n\n## Configuration\n\nIn 2.0.0 GA, `CommandLineBuilder` is removed. Configuration uses `ParserConfiguration` (for parsing) and `InvocationConfiguration` (for invocation).\n\n### Parser Configuration\n\n```csharp\nusing System.CommandLine;\n\nvar config = new ParserConfiguration\n{\n EnablePosixBundling = true, // -abc == -a -b -c (default: true)\n};\n\n// Response files enabled by default; disable with:\n// config.ResponseFileTokenReplacer = null;\n\nParseResult parseResult = rootCommand.Parse(args, config);\n```\n\n### Invocation Configuration\n\n```csharp\nvar invocationConfig = new InvocationConfiguration\n{\n // Redirect output for testing or customization\n Output = Console.Out,\n Error = Console.Error,\n\n // Process termination handling (default: 2 seconds)\n ProcessTerminationTimeout = TimeSpan.FromSeconds(5),\n\n // Disable default exception handler for custom try/catch\n EnableDefaultExceptionHandler = false\n};\n\nint exitCode = parseResult.Invoke(invocationConfig);\n```\n\n---\n\n## Tab Completion\n\n### Enabling Completion\n\nTab completion is built into RootCommand via the SuggestDirective (included by default).\n\nUsers register completions for their shell:\n\n```bash\n# Bash -- add to ~/.bashrc\nsource \u003c(mycli [suggest:bash])\n\n# Zsh -- add to ~/.zshrc\nsource \u003c(mycli [suggest:zsh])\n\n# PowerShell -- add to $PROFILE\nmycli [suggest:powershell] | Out-String | Invoke-Expression\n\n# Fish\nmycli [suggest:fish] | source\n```\n\n### Custom Completions\n\n```csharp\n// Static completions\nvar envOption = new Option\u003cstring>(\"--environment\");\nenvOption.CompletionSources.Add(\"development\", \"staging\", \"production\");\n\n// Dynamic completions\nvar branchOption = new Option\u003cstring>(\"--branch\");\nbranchOption.CompletionSources.Add(ctx =>\n[\n new CompletionItem(\"main\"),\n new CompletionItem(\"develop\"),\n // Dynamically fetch branches\n .. GetGitBranches().Select(b => new CompletionItem(b))\n]);\n```\n\n---\n\n## Automatic --version and --help\n\n### Version\n\n`--version` is automatically available on RootCommand via `VersionOption`. It reads from:\n1. `AssemblyInformationalVersionAttribute` (preferred -- includes SemVer metadata)\n2. `AssemblyVersionAttribute` (fallback)\n\n```xml\n\u003c!-- Set in .csproj for automatic --version output -->\n\u003cPropertyGroup>\n \u003cVersion>1.2.3\u003c/Version>\n \u003c!-- Or use source link / CI-generated version -->\n \u003cInformationalVersion>1.2.3+abc123\u003c/InformationalVersion>\n\u003c/PropertyGroup>\n```\n\n### Help\n\nHelp is automatically provided via `HelpOption` on RootCommand. Descriptions from constructors and `Description` properties flow into help text.\n\n---\n\n## Directives\n\nDirectives replace some beta-era `CommandLineBuilder` extensions. RootCommand exposes a `Directives` collection.\n\n```csharp\n// Built-in directives (included by default on RootCommand):\n// [suggest] -- tab completion suggestions\n// Other available directives:\nrootCommand.Directives.Add(new DiagramDirective()); // [diagram] -- shows parse tree\nrootCommand.Directives.Add(new EnvironmentVariablesDirective()); // [env:VAR=value]\n```\n\n### Parse Error Handling\n\n```csharp\n// Customize parse error behavior\nParseResult result = rootCommand.Parse(args);\nif (result.Action is ParseErrorAction parseError)\n{\n parseError.ShowTypoCorrections = true;\n parseError.ShowHelp = false;\n}\nint exitCode = result.Invoke();\n```\n\n---\n\n## Dependency Injection Pattern\n\nThe `System.CommandLine.Hosting` package is discontinued in 2.0.0 GA. For DI integration, use `Microsoft.Extensions.Hosting` directly and compose services before parsing.\n\n```csharp\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing System.CommandLine;\n\nvar host = Host.CreateDefaultBuilder(args)\n .ConfigureServices(services =>\n {\n services.AddSingleton\u003cISyncService, SyncService>();\n services.AddSingleton\u003cIFileSystem, PhysicalFileSystem>();\n })\n .Build();\n\nvar serviceProvider = host.Services;\n\nvar sourceOption = new Option\u003cstring>(\"--source\") { Description = \"Source endpoint\" };\nvar syncCommand = new Command(\"sync\", \"Synchronize data\");\nsyncCommand.Options.Add(sourceOption);\n\nsyncCommand.SetAction(async (ParseResult parseResult, CancellationToken ct) =>\n{\n var syncService = serviceProvider.GetRequiredService\u003cISyncService>();\n var source = parseResult.GetValue(sourceOption);\n await syncService.SyncAsync(source!, ct);\n return 0;\n});\n\nvar rootCommand = new RootCommand(\"My CLI tool\");\nrootCommand.Subcommands.Add(syncCommand);\n\nreturn await rootCommand.Parse(args).InvokeAsync();\n```\n\n---\n\n## Testing\n\n### Testing with InvocationConfiguration (TextWriter Capture)\n\n`IConsole` is removed in 2.0.0 GA. For testing, redirect output via `InvocationConfiguration`.\n\n```csharp\n[Fact]\npublic void ListCommand_WritesItems_ToOutput()\n{\n // Arrange\n var outputWriter = new StringWriter();\n var errorWriter = new StringWriter();\n var config = new InvocationConfiguration\n {\n Output = outputWriter,\n Error = errorWriter\n };\n\n var rootCommand = BuildRootCommand();\n\n // Act\n ParseResult parseResult = rootCommand.Parse(\"list --format json\");\n int exitCode = parseResult.Invoke(config);\n\n // Assert\n Assert.Equal(0, exitCode);\n Assert.Contains(\"json\", outputWriter.ToString());\n Assert.Empty(errorWriter.ToString());\n}\n```\n\n### Testing Parsed Values Without Invocation\n\n```csharp\n[Fact]\npublic void ParseResult_ExtractsOptionValues()\n{\n var portOption = new Option\u003cint>(\"--port\") { DefaultValueFactory = _ => 8080 };\n var rootCommand = new RootCommand { portOption };\n\n ParseResult result = rootCommand.Parse(\"--port 3000\");\n\n Assert.Equal(3000, result.GetValue(portOption));\n Assert.Empty(result.Errors);\n}\n\n[Fact]\npublic void ParseResult_ReportsErrors_ForInvalidInput()\n{\n var portOption = new Option\u003cint>(\"--port\");\n var rootCommand = new RootCommand { portOption };\n\n ParseResult result = rootCommand.Parse(\"--port not-a-number\");\n\n Assert.NotEmpty(result.Errors);\n}\n```\n\n### Testing Custom Parsers\n\n```csharp\n[Fact]\npublic void CustomParser_ParsesConnectionInfo()\n{\n var connOption = new Option\u003cConnectionInfo?>(\"--connection\")\n {\n CustomParser = result =>\n {\n var parts = result.Tokens.Single().Value.Split(':');\n return new ConnectionInfo(parts[0], int.Parse(parts[1]));\n }\n };\n var rootCommand = new RootCommand { connOption };\n\n ParseResult result = rootCommand.Parse(\"--connection localhost:5432\");\n\n var conn = result.GetValue(connOption);\n Assert.Equal(\"localhost\", conn!.Host);\n Assert.Equal(5432, conn.Port);\n}\n```\n\n### Testing with DI Services\n\n```csharp\n[Fact]\npublic async Task SyncCommand_CallsService()\n{\n var mockService = new Mock\u003cISyncService>();\n var services = new ServiceCollection()\n .AddSingleton(mockService.Object)\n .BuildServiceProvider();\n\n var sourceOption = new Option\u003cstring>(\"--source\");\n var syncCommand = new Command(\"sync\") { sourceOption };\n syncCommand.SetAction(async (ParseResult pr, CancellationToken ct) =>\n {\n var svc = services.GetRequiredService\u003cISyncService>();\n await svc.SyncAsync(pr.GetValue(sourceOption)!, ct);\n return 0;\n });\n\n var root = new RootCommand { syncCommand };\n int exitCode = await root.Parse(\"sync --source https://api.example.com\")\n .InvokeAsync();\n\n Assert.Equal(0, exitCode);\n mockService.Verify(s => s.SyncAsync(\"https://api.example.com\",\n It.IsAny\u003cCancellationToken>()), Times.Once);\n}\n```\n\n---\n\n## Response Files\n\nSystem.CommandLine supports response files (`@filename`) for passing large sets of arguments. Response file support is enabled by default; disable via `ParserConfiguration.ResponseFileTokenReplacer = null`.\n\n```bash\n# args.rsp\n--source https://api.example.com\n--output /tmp/results.json\n--verbose\n\n# Invoke with response file\nmycli sync @args.rsp\n```\n\n---\n\n## Migration from Beta4 to 2.0.0 GA\n\n| Beta4 API | 2.0.0 GA Replacement |\n|---|---|\n| `command.SetHandler(...)` | `command.SetAction(...)` |\n| `command.AddOption(opt)` | `command.Options.Add(opt)` |\n| `command.AddCommand(sub)` | `command.Subcommands.Add(sub)` |\n| `command.AddArgument(arg)` | `command.Arguments.Add(arg)` |\n| `option.AddAlias(\"-x\")` | `option.Aliases.Add(\"-x\")` |\n| `option.AddValidator(...)` | `option.Validators.Add(...)` |\n| `option.IsRequired = true` | `option.Required = true` |\n| `option.IsHidden = true` | `option.Hidden = true` |\n| `InvocationContext context` | `ParseResult parseResult` (in SetAction) |\n| `context.GetCancellationToken()` | `CancellationToken ct` (second param in async SetAction) |\n| `context.Console` | `InvocationConfiguration.Output / .Error` |\n| `IConsole` / `TestConsole` | `StringWriter` via `InvocationConfiguration` |\n| `new CommandLineBuilder(root).UseDefaults().Build()` | `root.Parse(args)` (middleware built-in) |\n| `builder.AddMiddleware(...)` | Removed -- use `ParseResult.Action` inspection or wrap `Invoke` |\n| `CommandLineBuilder` | `ParserConfiguration` + `InvocationConfiguration` |\n| `UseCommandHandler\u003cT,T>` (Hosting) | Build host directly, resolve services in SetAction |\n| `Parser` class | `CommandLineParser` (static class) |\n| `FindResultFor(symbol)` | `GetResult(symbol)` |\n| `ErrorMessage = \"...\"` | `result.AddError(\"...\")` |\n| `getDefaultValue: () => val` | `DefaultValueFactory = _ => val` |\n| `ParseArgument\u003cT>` delegate | `CustomParser` property |\n\n---\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":21424,"content_sha256":"06deffa432106f5ba43433dcc75d93f6ff55b6643a91c25bac8661139ba5dd76"},{"filename":"references/terminal-gui.md","content":"# Terminal.Gui\n\nTerminal.Gui v2 for building full terminal user interfaces with windows, menus, dialogs, views, layout, event handling, color themes, and mouse support. Cross-platform across Windows, macOS, and Linux terminals.\n\n**Version assumptions:** .NET 8.0+ baseline. Terminal.Gui 2.0.0-alpha (v2 Alpha is the active development line for new projects -- API is stable with comprehensive features; breaking changes possible before Beta but core architecture is solid). v1.x (1.19.0) is in maintenance mode with no new features.\n\nFor detailed code examples (views, menus, dialogs, events, themes, complete editor), see the Detailed Examples section below.\n\n## Package Reference\n\n```xml\n\u003cItemGroup>\n \u003cPackageReference Include=\"Terminal.Gui\" Version=\"2.0.0-alpha.*\" />\n\u003c/ItemGroup>\n```\n\n\n## Application Lifecycle\n\nTerminal.Gui v2 uses an instance-based model with `IApplication` and `IDisposable` for proper resource cleanup.\n\n### Basic Application\n\n```csharp\nusing Terminal.Gui;\n\nusing IApplication app = Application.Create().Init();\n\nvar window = new Window\n{\n Title = \"My TUI App\",\n Width = Dim.Fill(),\n Height = Dim.Fill()\n};\n\nvar label = new Label\n{\n Text = \"Hello, Terminal.Gui!\",\n X = Pos.Center(),\n Y = Pos.Center()\n};\nwindow.Add(label);\n\napp.Run(window);\n```\n\n\n## Layout System\n\nTerminal.Gui v2 unifies layout into a single model. Position is controlled by `Pos` (X, Y) and size by `Dim` (Width, Height), both relative to the SuperView's content area.\n\n### Pos Types (Positioning)\n\n```csharp\nview.X = 5; // Absolute\nview.X = Pos.Percent(25); // 25% from left\nview.X = Pos.Center(); // Centered\nview.X = Pos.AnchorEnd(10); // 10 from right edge\nview.X = Pos.Right(otherView) + 1; // Relative to another view\nview.Y = Pos.Bottom(otherView) + 1;\nview.X = Pos.Align(Alignment.End); // Align groups\nview.X = Pos.Func(() => CalculateX());\n```\n\n### Dim Types (Sizing)\n\n```csharp\nview.Width = 40; // Absolute\nview.Width = Dim.Percent(50); // 50% of parent\nview.Width = Dim.Fill(); // Fill remaining space\nview.Width = Dim.Auto(); // Size based on content\nview.Width = Dim.Auto(minimumContentDim: 20);\nview.Width = Dim.Width(otherView); // Relative to another view\nview.Width = Dim.Func(() => CalculateWidth());\n```\n\n### Frame vs. Viewport\n\n- **Frame** -- outermost rectangle: location and size relative to SuperView\n- **Viewport** -- visible portion of content area: acts as a scrollable portal into the view's content\n\n\n## Cross-Platform Considerations\n\n| Feature | Windows Terminal | macOS Terminal.app | Linux (xterm/gnome) |\n|---|---|---|---|\n| TrueColor (24-bit) | Yes | Yes | Yes (most) |\n| Mouse support | Yes | Yes | Yes |\n| Unicode/emoji | Yes | Yes | Varies |\n| Key modifiers | Full | Limited | Full |\n\n### Platform-Specific Notes\n\n- **macOS Terminal.app** -- limited modifier key support; iTerm2 and WezTerm provide better support\n- **SSH sessions** -- terminal capabilities depend on the client terminal, not the server\n- **Windows Console Host** -- legacy conhost has limited Unicode support; Windows Terminal provides full support\n- **tmux/screen** -- may intercept key combinations; set `TERM=xterm-256color`\n\n\n## Agent Gotchas\n\n1. **Do not use v1 static lifecycle pattern.** v2 uses instance-based `Application.Create().Init()` with `IDisposable`. Always wrap in a `using` statement.\n2. **Do not use `View.AutoSize`.** Removed in v2. Use `Dim.Auto()` instead.\n3. **Do not confuse Frame with Viewport.** Frame is the outer rectangle; Viewport is the visible content area (supports scrolling).\n4. **Do not use `Button.Clicked`.** Replaced by `Button.Accepting` in v2.\n5. **Do not call UI operations from background threads.** Terminal.Gui is single-threaded. Use `Application.Invoke()` to marshal calls back to the UI thread.\n6. **Do not forget `RequestStop()` to close windows.** Calling `Dispose()` directly corrupts terminal state.\n7. **Do not hardcode terminal dimensions.** Use `Dim.Fill()`, `Dim.Percent()`, and `Pos.Center()` for responsive layouts.\n8. **Do not ignore terminal state on crash.** Wrap `app.Run()` in try/catch and ensure the `using` block disposes the application.\n9. **Do not use `ScrollView`.** Removed in v2. All views now support scrolling natively via `SetContentSize()`.\n10. **Do not use `NStack.ustring`.** Removed in v2. Use standard `System.String`.\n11. **Do not use `StatusItem`.** Removed in v2. Use `Shortcut` objects with `StatusBar.Add()` instead.\n\n\n## Prerequisites\n\n- **NuGet package:** `Terminal.Gui` 2.0.0-alpha (v2) or 1.19.x (v1 maintenance)\n- **Target framework:** net8.0 or later\n- **Terminal:** Any terminal emulator supporting ANSI escape sequences\n\n\n## References\n\n- [Terminal.Gui GitHub](https://github.com/gui-cs/Terminal.Gui)\n- [Terminal.Gui v2 Documentation](https://gui-cs.github.io/Terminal.Gui/)\n- [Terminal.Gui NuGet](https://www.nuget.org/packages/Terminal.Gui)\n- [Terminal.Gui v2 What's New](https://gui-cs.github.io/Terminal.Gui/docs/newinv2)\n- [v1 to v2 Migration Guide](https://github.com/gui-cs/Terminal.Gui/blob/v2_develop/docfx/docs/migratingfromv1.md)\n\n---\n\n# Terminal.Gui -- Detailed Examples\n\nExtended code examples for Terminal.Gui v2 views, layout, menus, dialogs, event handling, themes, and complete application patterns.\n\n---\n\n## Core Views\n\n### Container Views\n\n```csharp\nvar window = new Window\n{\n Title = \"Main Window\",\n Width = Dim.Fill(),\n Height = Dim.Fill()\n};\n\nvar frame = new FrameView\n{\n Title = \"Settings\",\n X = 1, Y = 1,\n Width = Dim.Fill(1),\n Height = 10\n};\nwindow.Add(frame);\n```\n\n### Text and Input Views\n\n```csharp\nvar label = new Label\n{\n Text = \"Username:\",\n X = 1, Y = 1\n};\n\nvar textField = new TextField\n{\n X = Pos.Right(label) + 1,\n Y = Pos.Top(label),\n Width = 30,\n Text = \"\"\n};\n\nvar textView = new TextView\n{\n X = 1, Y = 3,\n Width = Dim.Fill(1),\n Height = Dim.Fill(1),\n Text = \"Multi-line\\nediting area\"\n};\n```\n\n### Button\n\n```csharp\nvar button = new Button\n{\n Text = \"OK\",\n X = Pos.Center(),\n Y = Pos.Bottom(textField) + 1\n};\n\nbutton.Accepting += (sender, args) =>\n{\n MessageBox.Query(button.App!, \"Info\", $\"You entered: {textField.Text}\", \"OK\");\n args.Handled = true;\n};\n```\n\n### ListView and TableView\n\n```csharp\nvar items = new List\u003cstring> { \"Item 1\", \"Item 2\", \"Item 3\" };\nvar listView = new ListView\n{\n X = 1, Y = 1,\n Width = Dim.Fill(1),\n Height = Dim.Fill(1),\n Source = new ListWrapper\u003cstring>(new ObservableCollection\u003cstring>(items))\n};\n\nlistView.SelectedItemChanged += (sender, args) =>\n{\n // args.Value is the selected item index\n};\n```\n\n### CheckBox and RadioGroup\n\n```csharp\nvar checkbox = new CheckBox\n{\n Text = \"Enable notifications\",\n X = 1, Y = 1\n};\n\ncheckbox.CheckedStateChanging += (sender, args) =>\n{\n // args.NewValue is the new CheckState\n};\n\nvar radioGroup = new RadioGroup\n{\n X = 1, Y = 3,\n RadioLabels = [\"Option A\", \"Option B\", \"Option C\"]\n};\n```\n\n### Additional v2 Views\n\n```csharp\nvar datePicker = new DatePicker\n{\n X = 1, Y = 1,\n Date = DateTime.Today\n};\n\nvar spinner = new NumericUpDown\u003cint>\n{\n X = 1, Y = 3,\n Value = 42\n};\n\nvar colorPicker = new ColorPicker\n{\n X = 1, Y = 5,\n SelectedColor = new Color(0, 120, 215)\n};\n```\n\n---\n\n## Menus and Status Bar\n\n### MenuBar\n\n```csharp\nvar menuBar = new MenuBar([\n new MenuBarItem(\"_File\",\n [\n new MenuItem(\"_New\", \"Create new file\", () => NewFile()),\n new MenuItem(\"_Open\", \"Open existing file\", () => OpenFile()),\n new MenuBarItem(\"_Recent\",\n [\n new MenuItem(\"file1.txt\", \"\", () => Open(\"file1.txt\")),\n new MenuItem(\"file2.txt\", \"\", () => Open(\"file2.txt\"))\n ]),\n null, // separator\n new MenuItem\n {\n Title = \"_Quit\",\n HelpText = \"Exit application\",\n Key = Application.QuitKey,\n Command = Command.Quit\n }\n ]),\n new MenuBarItem(\"_Edit\",\n [\n new MenuItem(\"_Copy\", \"\", () => Copy(), Key.C.WithCtrl),\n new MenuItem(\"_Paste\", \"\", () => Paste(), Key.V.WithCtrl)\n ]),\n new MenuBarItem(\"_Help\",\n [\n new MenuItem(\"_About\", \"About this app\", () =>\n MessageBox.Query(app, \"\", \"My TUI App v1.0\", \"OK\"))\n ])\n]);\nwindow.Add(menuBar);\n```\n\n### StatusBar\n\n```csharp\nvar statusBar = new StatusBar();\n\nvar helpShortcut = new Shortcut\n{\n Title = \"Help\",\n Key = Key.F1,\n CanFocus = false\n};\nhelpShortcut.Accepting += (sender, args) =>\n{\n ShowHelp();\n args.Handled = true;\n};\n\nvar saveShortcut = new Shortcut\n{\n Title = \"Save\",\n Key = Key.F2,\n CanFocus = false\n};\nsaveShortcut.Accepting += (sender, args) =>\n{\n Save();\n args.Handled = true;\n};\n\nvar quitShortcut = new Shortcut\n{\n Title = \"Quit\",\n Key = Application.QuitKey,\n CanFocus = false\n};\n\nstatusBar.Add(helpShortcut, saveShortcut, quitShortcut);\nwindow.Add(statusBar);\n```\n\n---\n\n## Dialogs and MessageBox\n\n### Dialog\n\n```csharp\nvar dialog = new Dialog\n{\n Title = \"Confirm\",\n Width = 50,\n Height = 10\n};\n\nvar label = new Label\n{\n Text = \"Are you sure?\",\n X = Pos.Center(),\n Y = 1\n};\ndialog.Add(label);\n\nvar okButton = new Button { Text = \"OK\" };\nokButton.Accepting += (sender, args) =>\n{\n dialog.RequestStop();\n args.Handled = true;\n};\n\nvar cancelButton = new Button { Text = \"Cancel\" };\ncancelButton.Accepting += (sender, args) =>\n{\n dialog.RequestStop();\n args.Handled = true;\n};\n\ndialog.AddButton(okButton);\ndialog.AddButton(cancelButton);\napp.Run(dialog);\n```\n\n### MessageBox\n\n```csharp\nint result = MessageBox.Query(app, \"Confirm Delete\",\n \"Delete this file permanently?\",\n \"Yes\", \"No\");\n\nif (result == 0)\n{\n // User clicked \"Yes\"\n}\n\nMessageBox.ErrorQuery(app, \"Error\",\n \"Failed to save file.\\nCheck permissions.\",\n \"OK\");\n```\n\n### FileDialog\n\n```csharp\nvar fileDialog = new FileDialog\n{\n Title = \"Open File\",\n AllowedTypes = [new AllowedType(\"C# Files\", \".cs\", \".csx\")],\n MustExist = true\n};\n\napp.Run(fileDialog);\n\nif (!fileDialog.Canceled)\n{\n string selectedPath = fileDialog.FilePath;\n}\n```\n\n---\n\n## Event Handling and Key Bindings\n\n### Key Bindings with Commands\n\n```csharp\nview.AddCommand(Command.Accept, (args) =>\n{\n return true;\n});\nview.KeyBindings.Add(Key.Enter, Command.Accept);\n\nview.KeyBindings.Add(Key.S.WithCtrl, Command.Save);\nview.AddCommand(Command.Save, (args) =>\n{\n SaveDocument();\n return true;\n});\n```\n\n### Key Event Handling\n\n```csharp\nview.KeyDown += (sender, args) =>\n{\n if (args.KeyCode == Key.F5)\n {\n RefreshData();\n args.Handled = true;\n }\n};\n```\n\n### Mouse Events\n\n```csharp\nview.MouseClick += (sender, args) =>\n{\n int col = args.Position.X;\n int row = args.Position.Y;\n};\n\nview.MouseEvent += (sender, args) =>\n{\n if (args.Flags.HasFlag(MouseFlags.Button1DoubleClicked))\n {\n // Handle double-click\n }\n};\n```\n\n---\n\n## Color Themes and Styling\n\n```csharp\nvar customColor = new Color(0xFF, 0x99, 0x00);\n\nvar attr = new Attribute(\n new Color(255, 255, 255),\n new Color(0, 0, 128)\n);\n\nview.ColorScheme = new ColorScheme\n{\n Normal = attr,\n Focus = new Attribute(Color.Black, Color.BrightCyan),\n HotNormal = new Attribute(Color.Red, Color.Blue),\n HotFocus = new Attribute(Color.BrightRed, Color.BrightCyan)\n};\n```\n\n### Theme Configuration\n\n```csharp\nConfigurationManager.RuntimeConfig = \"\"\"{ \"Theme\": \"Amber Phosphor\" }\"\"\";\nConfigurationManager.Enable(ConfigLocations.All);\nIApplication app = Application.Create().Init();\n```\n\n---\n\n## Adornments (Borders, Margins, Padding)\n\n```csharp\nvar view = new View\n{\n X = 1, Y = 1,\n Width = 40, Height = 10\n};\n\nview.Border.LineStyle = LineStyle.Rounded;\nview.Border.Thickness = new Thickness(1);\nview.Margin.Thickness = new Thickness(1);\nview.Padding.Thickness = new Thickness(1, 0);\n```\n\n---\n\n## Complete Example: Simple Editor\n\n```csharp\nusing Terminal.Gui;\n\nusing IApplication app = Application.Create().Init();\n\nvar window = new Window\n{\n Title = $\"Simple Editor ({Application.QuitKey} to quit)\",\n Width = Dim.Fill(),\n Height = Dim.Fill()\n};\n\nvar textView = new TextView\n{\n X = 0, Y = 1,\n Width = Dim.Fill(),\n Height = Dim.Fill(1),\n Text = \"\"\n};\n\nvar menuBar = new MenuBar([\n new MenuBarItem(\"_File\",\n [\n new MenuItem(\"_New\", \"Clear editor\", () => textView.Text = \"\"),\n null,\n new MenuItem\n {\n Title = \"_Quit\",\n HelpText = \"Exit\",\n Key = Application.QuitKey,\n Command = Command.Quit\n }\n ])\n]);\n\nvar statusBar = new StatusBar();\nvar helpShortcut = new Shortcut { Title = \"Help\", Key = Key.F1, CanFocus = false };\nhelpShortcut.Accepting += (s, e) =>\n{\n MessageBox.Query(app, \"Help\", \"Simple text editor.\", \"OK\");\n e.Handled = true;\n};\nstatusBar.Add(helpShortcut);\n\nwindow.Add(menuBar, textView, statusBar);\napp.Run(window);\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":12954,"content_sha256":"ecf2e49103859ce8172791ae0702797207058bfa49f54ac2f314f024b40e3a07"},{"filename":"references/tool-management.md","content":"# Tool Management\n\nConsumer-side management of .NET CLI tools: installing global and local tools, creating and maintaining `.config/dotnet-tools.json` manifests, version pinning for team reproducibility, `dotnet tool restore` in CI pipelines, updating and uninstalling tools, and troubleshooting common tool issues.\n\n**Version assumptions:** .NET 8.0+ baseline. Local tools and tool manifests available since .NET Core 3.0. RID-specific tool packaging available since .NET 10.\n\n## Global Tool Installation\n\nGlobal tools are installed per-user and available from any directory. The tool binaries are added to a directory on the user's PATH.\n\n```bash\n# Install a global tool\ndotnet tool install -g \u003cpackage-id>\n\n# Install a specific version\ndotnet tool install -g \u003cpackage-id> --version 1.2.3\n\n# Install a pre-release version\ndotnet tool install -g \u003cpackage-id> --version \"*-rc*\"\n\n# List installed global tools\ndotnet tool list -g\n\n# Update a global tool to the latest stable version\ndotnet tool update -g \u003cpackage-id>\n\n# Uninstall a global tool\ndotnet tool uninstall -g \u003cpackage-id>\n```\n\n**Default install locations:**\n\n| OS | Path |\n|-----|------|\n| Linux/macOS | `$HOME/.dotnet/tools` |\n| Windows | `%USERPROFILE%\\.dotnet\\tools` |\n\nGlobal tools are user-scoped, not machine-wide. Each user maintains their own tool installations independently.\n\n### Custom Install Location\n\nUse `--tool-path` to install to a custom directory. The directory is not automatically added to PATH -- you must manage PATH yourself:\n\n```bash\ndotnet tool install \u003cpackage-id> --tool-path ~/my-tools\n```\n\n\n## Local Tool Installation\n\nLocal tools are scoped to a directory tree and tracked in a manifest file. Different directories can use different versions of the same tool.\n\n### Creating the Tool Manifest\n\nThe manifest file `.config/dotnet-tools.json` tracks local tool versions. Create it at the repository root:\n\n```bash\n# Create the manifest (first time only, at repo root)\ndotnet new tool-manifest\n```\n\nThis produces:\n\n```json\n{\n \"version\": 1,\n \"isRoot\": true,\n \"tools\": {}\n}\n```\n\nCommit this file to source control so all team members share the same tool versions.\n\n### Installing Local Tools\n\nOmit the `-g` flag to install a tool locally. The tool is recorded in the nearest manifest file:\n\n```bash\n# Install a local tool (recorded in .config/dotnet-tools.json)\ndotnet tool install \u003cpackage-id>\n\n# Install a specific version\ndotnet tool install \u003cpackage-id> --version 2.0.1\n\n# List local tools\ndotnet tool list\n\n# Update a local tool\ndotnet tool update \u003cpackage-id>\n\n# Uninstall a local tool\ndotnet tool uninstall \u003cpackage-id>\n```\n\nAfter installing two tools, the manifest looks like:\n\n```json\n{\n \"version\": 1,\n \"isRoot\": true,\n \"tools\": {\n \"dotnet-ef\": {\n \"version\": \"9.0.3\",\n \"commands\": [\n \"dotnet-ef\"\n ]\n },\n \"nbgv\": {\n \"version\": \"3.7.112\",\n \"commands\": [\n \"nbgv\"\n ]\n }\n }\n}\n```\n\n### Running Local Tools\n\n```bash\n# Run a local tool (long form)\ndotnet tool run \u003ccommand-name>\n\n# Run a local tool (short form, when command starts with dotnet-)\ndotnet \u003ccommand-name>\n\n# Examples\ndotnet tool run dotnet-ef migrations add Init\ndotnet ef migrations add Init # equivalent short form\n```\n\n\n## Version Pinning and Team Workflows\n\nThe tool manifest enables reproducible tool versions across the team.\n\n### Pinning Strategy\n\n1. **One team member** creates the manifest and installs tools with specific versions\n2. **Commit** `.config/dotnet-tools.json` to source control\n3. **All team members** run `dotnet tool restore` after cloning or pulling\n4. **Updates** are explicit: one person runs `dotnet tool update \u003cpackage-id>`, commits the updated manifest\n\n### Version Ranges\n\nUse the `--version` option with NuGet version ranges for controlled flexibility:\n\n```bash\n# Exact version (strictest)\ndotnet tool install \u003cpackage-id> --version 2.0.1\n\n# Allow patch updates (recommended for most tools)\ndotnet tool install \u003cpackage-id> --version \"2.0.*\"\n\n# Pre-release versions\ndotnet tool install \u003cpackage-id> --version \"*-preview*\"\n```\n\nThe manifest always records the exact resolved version, ensuring all team members use identical versions after restore.\n\n\n## CI Integration\n\n### Tool Restore Before Build\n\nIn CI pipelines, restore tools before any build step that depends on them. Tool restore is fast and idempotent.\n\n**GitHub Actions:**\n\n```yaml\nsteps:\n - uses: actions/checkout@v4\n - uses: actions/setup-dotnet@v4\n with:\n dotnet-version: '8.0.x'\n - name: Restore tools\n run: dotnet tool restore\n - name: Build\n run: dotnet build\n - name: Run EF migrations check\n run: dotnet ef migrations has-pending-changes\n```\n\n**Azure DevOps Pipelines:**\n\n```yaml\nsteps:\n - task: UseDotNet@2\n inputs:\n packageType: sdk\n version: '8.0.x'\n - script: dotnet tool restore\n displayName: 'Restore tools'\n - script: dotnet build\n displayName: 'Build'\n```\n\n### CI Best Practices\n\n- **Always run `dotnet tool restore` before build** -- do not rely on tools being pre-installed on CI agents\n- **Commit `.config/dotnet-tools.json`** -- the manifest ensures CI uses the same tool versions as local development\n- **Do not install tools globally in CI** -- use local tool manifests for reproducibility; global installs may conflict across concurrent jobs\n- **Cache NuGet packages** to speed up tool restore (`~/.nuget/packages` on Linux/macOS, `%USERPROFILE%\\.nuget\\packages` on Windows)\n\n\n## RID-Specific Tools\n\nStarting with .NET 10, tool authors can publish RID-specific, self-contained, or Native AOT versions of their tools. From a consumer perspective, this is transparent -- the `dotnet tool install` command automatically selects the best package for your platform.\n\n```bash\n# RID selection is automatic -- no extra flags needed\ndotnet tool install -g \u003cpackage-id>\n```\n\nThe .NET CLI detects your platform and downloads the appropriate RID-specific package. If no RID-specific package matches your platform, the CLI falls back to a framework-dependent package (if the tool author provided one).\n\nFor details on authoring and packaging RID-specific tools, see `references/cli-distribution.md`.\n\n\n## Troubleshooting\n\n### Common Issues\n\n**\"Tool already installed\"** -- Uninstall first or use `dotnet tool update`:\n\n```bash\ndotnet tool update -g \u003cpackage-id>\n```\n\n**\"No manifest file found\"** -- Run `dotnet new tool-manifest` to create one, or check that you are in a directory at or below the manifest location.\n\n**\"Tool not found after install\"** -- For global tools, verify `~/.dotnet/tools` is on your PATH. For local tools, ensure you are in the directory tree containing the manifest.\n\n**\"Version mismatch in CI\"** -- Verify `.config/dotnet-tools.json` is committed and `dotnet tool restore` runs before any tool usage.\n\n\n## Global vs Local Tools Decision Guide\n\n| Aspect | Global Tool | Local Tool |\n|--------|------------|------------|\n| Scope | System-wide (per user) | Per-project directory tree |\n| Install location | `~/.dotnet/tools` | `.config/dotnet-tools.json` manifest |\n| Version management | Manual `dotnet tool update -g` | Tracked in source control |\n| CI/CD | Must install before use (not reproducible) | `dotnet tool restore` restores all (reproducible) |\n| Team consistency | Each developer manages independently | Manifest ensures identical versions |\n| Best for | Personal productivity tools, one-off utilities | Project-specific build/dev tools |\n\n**Prefer local tools** for anything used in a project's build, test, or development workflow. Reserve global tools for personal utilities not tied to a specific project.\n\n\n## Agent Gotchas\n\n1. **Do not install project-specific tools globally in CI.** Use local tool manifests and `dotnet tool restore` for reproducible builds. Global installs may conflict across concurrent CI jobs and drift from the team's pinned versions.\n2. **Do not skip `dotnet tool restore` in CI pipelines.** Tools are not pre-installed on CI agents. Always restore before any step that invokes a local tool, or the build will fail with \"tool not found.\"\n3. **Do not omit `.config/dotnet-tools.json` from source control.** The manifest is the single source of truth for tool versions. Without it, `dotnet tool restore` has nothing to restore and each developer gets different versions.\n4. **Do not specify RID flags when installing tools as a consumer.** The .NET CLI automatically selects the correct RID-specific package for your platform. Manual RID selection is unnecessary and may cause installation failures.\n5. **Do not confuse tool command names with package IDs.** The package ID (e.g., `dotnet-ef`) may differ from the command name (e.g., `dotnet ef`). Use `dotnet tool list` to see the mapping between package IDs and commands.\n\n\n## References\n\n- [.NET tools overview](https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools)\n- [How to manage .NET local tools](https://learn.microsoft.com/en-us/dotnet/core/tools/local-tools-how-to-use)\n- [dotnet tool install command](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-tool-install)\n- [dotnet tool restore command](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-tool-restore)\n- [RID-specific .NET tools](https://learn.microsoft.com/en-us/dotnet/core/tools/rid-specific-tools)\n- [Troubleshoot .NET tool usage issues](https://learn.microsoft.com/en-us/dotnet/core/tools/troubleshoot-usage-issues)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":9398,"content_sha256":"5451ba4fa26f292d68a458be1a7e04e0632e0e7f591e3ecacf55e8ee2a1e5bd0"},{"filename":"references/trimming.md","content":"# Trimming\n\nTrim-safe development for .NET 8+ applications and libraries: trimming annotations (`[RequiresUnreferencedCode]`, `[DynamicallyAccessedMembers]`, `[DynamicDependency]`), ILLink descriptor XML for type preservation, `TrimmerSingleWarn` for granular diagnostics, testing trimmed output, fixing IL2xxx/IL3xxx warnings, and library authoring with `IsTrimmable`.\n\n**Version assumptions:** .NET 8.0+ baseline. Trimming shipped in .NET 6, but .NET 8 provides the most complete annotation surface and analyzer coverage. .NET 9 improved warning accuracy and library compat.\n\n## MSBuild Properties: Apps vs Libraries\n\nApps and libraries use different MSBuild properties for trimming. This distinction is critical -- using the wrong property causes subtle issues.\n\n### For Applications\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- Enable trimming on publish -->\n \u003cPublishTrimmed>true\u003c/PublishTrimmed>\n\n \u003c!-- Enable trim analyzer during development -->\n \u003cEnableTrimAnalyzer>true\u003c/EnableTrimAnalyzer>\n\n \u003c!-- Optional: also enable AOT analyzer if targeting AOT -->\n \u003cEnableAotAnalyzer>true\u003c/EnableAotAnalyzer>\n\u003c/PropertyGroup>\n```\n\n`PublishTrimmed` tells the linker to remove unreachable code when publishing. `EnableTrimAnalyzer` enables Roslyn analyzers that warn about trim-unsafe patterns during development.\n\n### For Libraries\n\n```xml\n\u003cPropertyGroup>\n \u003c!-- Declare the library is trim-safe (auto-enables trim analyzer) -->\n \u003cIsTrimmable>true\u003c/IsTrimmable>\n\n \u003c!-- Declare AOT compatibility (auto-enables AOT analyzer) -->\n \u003cIsAotCompatible>true\u003c/IsAotCompatible>\n\u003c/PropertyGroup>\n```\n\n**Key difference:** Libraries do not set `PublishTrimmed` -- they are not published as standalone applications. `IsTrimmable` tells consumers that the library's public API is annotated for trimming safety. Setting `IsTrimmable` automatically enables the trim analyzer for the library project.\n\n| Property | Project Type | Effect |\n|----------|-------------|--------|\n| `PublishTrimmed` | App | Trims on publish, enables linker |\n| `EnableTrimAnalyzer` | App | Enables trim warnings during build |\n| `IsTrimmable` | Library | Declares trim-safe, auto-enables analyzer |\n| `IsAotCompatible` | Library | Declares AOT-safe, auto-enables AOT analyzer |\n| `PublishAot` | App | Enables AOT (implies `PublishTrimmed`) |\n\n\n## Trimming Annotations\n\n.NET provides attributes to annotate code that interacts with reflection, helping the trimmer understand what to preserve.\n\n### `[RequiresUnreferencedCode]`\n\nMarks a method as unsafe for trimming. The trimmer and analyzer produce IL2026 warnings when this method is called from trim-safe code.\n\n```csharp\n[RequiresUnreferencedCode(\"Uses reflection to discover plugins\")]\npublic IPlugin LoadPlugin(string typeName)\n{\n var type = Type.GetType(typeName)\n ?? throw new InvalidOperationException($\"Type {typeName} not found\");\n return (IPlugin)Activator.CreateInstance(type)!;\n}\n```\n\n### `[DynamicallyAccessedMembers]`\n\nTells the trimmer which members of a type are accessed via reflection, so they are preserved:\n\n```csharp\npublic T CreateInstance\u003c[DynamicallyAccessedMembers(\n DynamicallyAccessedMemberTypes.PublicConstructors)] T>()\n where T : class\n => (T)Activator.CreateInstance(typeof(T))!;\n\n// The trimmer preserves public constructors of T\n// because the constraint tells it what's needed\n```\n\n### `[DynamicDependency]`\n\nExplicitly preserves a specific member from trimming:\n\n```csharp\n// Preserve a method that is only called via reflection\n[DynamicDependency(nameof(OnConfigChanged), typeof(ConfigWatcher))]\npublic void StartWatching() { /* reflects on OnConfigChanged */ }\n\n// Preserve all public properties (e.g., for serialization)\n[DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties,\n typeof(LegacyDto))]\npublic void SerializeLegacy(LegacyDto dto) { /* ... */ }\n```\n\n### `[UnconditionalSuppressMessage]`\n\nSuppresses a specific trim warning when you have verified the code is safe despite the analyzer's concern:\n\n```csharp\n[UnconditionalSuppressMessage(\"Trimming\",\n \"IL2026:RequiresUnreferencedCode\",\n Justification = \"Type is preserved via ILLink descriptor\")]\npublic void CallLegacyCode() { /* ... */ }\n```\n\nUse sparingly -- only when you have verified safety through ILLink descriptors or other means.\n\n\n## ILLink Descriptors\n\nILLink descriptor XML files tell the trimmer to preserve types, methods, or entire assemblies. **Do not use legacy RD.xml** -- it is a .NET Native/UWP format that is silently ignored by modern .NET trimming.\n\n### Descriptor Format\n\n```xml\n\u003c!-- ILLink.Descriptors.xml -->\n\u003clinker>\n \u003c!-- Preserve specific types -->\n \u003cassembly fullname=\"MyApp\">\n \u003ctype fullname=\"MyApp.Models.PluginConfig\" preserve=\"all\" />\n \u003ctype fullname=\"MyApp.Services.LegacyAdapter\">\n \u003cmethod name=\"Initialize\" />\n \u003cmethod name=\"ProcessRequest\" />\n \u003c/type>\n \u003c/assembly>\n\n \u003c!-- Preserve an entire third-party assembly -->\n \u003cassembly fullname=\"LegacyLibrary\" preserve=\"all\" />\n\u003c/linker>\n```\n\n### Registration\n\n```xml\n\u003c!-- In .csproj -->\n\u003cItemGroup>\n \u003cTrimmerRootDescriptor Include=\"ILLink.Descriptors.xml\" />\n\u003c/ItemGroup>\n```\n\n### Alternative: TrimmerRootAssembly\n\nFor entire assemblies that must not be trimmed:\n\n```xml\n\u003cItemGroup>\n \u003c!-- Preserve entire assembly (no trimming at all) -->\n \u003cTrimmerRootAssembly Include=\"LegacyLibrary\" />\n\u003c/ItemGroup>\n```\n\n\n## TrimmerSingleWarn\n\nBy default, the trimmer groups warnings per assembly, showing one summary line. `TrimmerSingleWarn=false` shows every individual warning, which is essential for fixing trim issues.\n\n```bash\n# Default: one warning per assembly (hard to debug)\ndotnet publish -c Release /p:PublishTrimmed=true\n# warning IL2104: Assembly 'MyApp' produced trim warnings\n\n# Detailed: per-occurrence warnings (easier to fix)\ndotnet publish -c Release /p:PublishTrimmed=true /p:TrimmerSingleWarn=false\n# warning IL2026: MyApp.PluginLoader.LoadPlugin(...) requires unreferenced code\n# warning IL2057: Unrecognized value passed to Type.GetType(...)\n\n# Analysis without publishing\ndotnet build /p:EnableTrimAnalyzer=true /p:TrimmerSingleWarn=false\n```\n\n\n## IL2xxx/IL3xxx Warning Reference\n\n### Trim Warnings (IL2xxx)\n\n| Code | Meaning | Fix |\n|------|---------|-----|\n| IL2026 | Method has `[RequiresUnreferencedCode]` | Replace with trim-safe alternative or add descriptor |\n| IL2046 | Trim attribute mismatch on override | Match annotation from base type |\n| IL2057 | Unrecognized `Type.GetType()` argument | Use compile-time known type or `[DynamicDependency]` |\n| IL2060 | `MakeGenericType` call with unknown type | Use concrete generic instantiations |\n| IL2062 | Value passed to `[DynamicallyAccessedMembers]` parameter has no annotation | Add `[DynamicallyAccessedMembers]` to the source |\n| IL2067 | Parameter mismatch for `[DynamicallyAccessedMembers]` | Ensure annotations flow correctly through call chain |\n| IL2070 | `this` parameter of `Type.GetProperties()` etc. not annotated | Add `[DynamicallyAccessedMembers]` constraint |\n| IL2072 | Return value of a method not annotated | Annotate return type with `[DynamicallyAccessedMembers]` |\n| IL2104 | Assembly produced trim warnings (summary) | Use `TrimmerSingleWarn=false` for details |\n\n### AOT Warnings (IL3xxx)\n\n| Code | Meaning | Fix |\n|------|---------|-----|\n| IL3050 | Method has `[RequiresDynamicCode]` | Replace with source-gen or static alternative |\n| IL3051 | `[RequiresDynamicCode]` annotation mismatch | Match annotation from base type |\n| IL3052 | COM interop with dynamic code | Use `[LibraryImport]` with static marshalling |\n\n\n## Testing Trimmed Output\n\n### Publish and Test\n\n```bash\n# Publish with trimming\ndotnet publish -c Release -r linux-x64 /p:PublishTrimmed=true\n\n# Run the trimmed binary\n./bin/Release/net8.0/linux-x64/publish/MyApp\n\n# Verify functionality:\n# 1. All endpoints respond correctly\n# 2. JSON deserialization produces populated objects\n# 3. DI-resolved services function\n# 4. No MissingMethodException or MissingMetadataException\n```\n\n### Trim Test in CI\n\n```bash\n# CI script: publish trimmed and run integration tests\ndotnet publish src/MyApp -c Release -r linux-x64 /p:PublishTrimmed=true -o ./publish\n\n# Run smoke tests against trimmed binary\n./publish/MyApp &\nAPP_PID=$!\nsleep 3\n\ncurl -f http://localhost:8080/health/live || (kill $APP_PID; exit 1)\ncurl -f http://localhost:8080/api/products || (kill $APP_PID; exit 1)\n\nkill $APP_PID\n```\n\n### Trim Warning CI Gate\n\n```bash\n# Fail CI if any trim warnings exist\ndotnet build /p:EnableTrimAnalyzer=true /p:TrimmerSingleWarn=false \\\n /warnaserror:IL2026,IL2057,IL2060,IL2067,IL2070,IL3050\n```\n\n\n## Library Authoring for Trimming\n\n### Making a Library Trim-Safe\n\n1. Set `\u003cIsTrimmable>true\u003c/IsTrimmable>` in the library `.csproj`\n2. Annotate all reflection-using APIs with `[RequiresUnreferencedCode]`\n3. Add `[DynamicallyAccessedMembers]` to parameters that receive types used reflectively\n4. Replace reflection with source generators where possible\n5. Test by consuming the library from a trimmed application\n\n```xml\n\u003c!-- Library .csproj -->\n\u003cPropertyGroup>\n \u003c!-- Auto-enables trim analyzer -->\n \u003cIsTrimmable>true\u003c/IsTrimmable>\n \u003c!-- Auto-enables AOT analyzer; implies IsTrimmable in .NET 8+ -->\n \u003cIsAotCompatible>true\u003c/IsAotCompatible>\n\u003c/PropertyGroup>\n```\n\n### Annotating Public APIs\n\n```csharp\n// Method that uses reflection internally -- annotate honestly\n[RequiresUnreferencedCode(\n \"Uses reflection to discover plugin types. \" +\n \"Use RegisterPlugin\u003cT>() for trim-safe plugin registration.\")]\npublic IPlugin LoadPlugin(string typeName) { /* ... */ }\n\n// Trim-safe alternative\npublic void RegisterPlugin\u003c[DynamicallyAccessedMembers(\n DynamicallyAccessedMemberTypes.PublicConstructors)] T>()\n where T : class, IPlugin\n{\n _plugins[typeof(T).Name] = () => (IPlugin)Activator.CreateInstance\u003cT>();\n}\n```\n\n### Conditional APIs\n\nProvide both reflection-based and trim-safe APIs when possible:\n\n```csharp\npublic class ServiceRegistry\n{\n // Trim-safe: explicit type\n public void Register\u003c[DynamicallyAccessedMembers(\n DynamicallyAccessedMemberTypes.PublicConstructors)] TService,\n TImplementation>()\n where TImplementation : class, TService\n { /* ... */ }\n\n // Not trim-safe: assembly scanning\n [RequiresUnreferencedCode(\"Scans assembly for service types\")]\n public void RegisterFromAssembly(Assembly assembly)\n { /* ... */ }\n}\n```\n\n\n## Agent Gotchas\n\n1. **Do not use `PublishTrimmed` in library projects.** Libraries use `IsTrimmable` to declare they are trim-safe. `PublishTrimmed` is for applications.\n2. **Do not use RD.xml for type preservation.** RD.xml is a .NET Native/UWP format that is silently ignored by modern .NET trimming. Use ILLink descriptor XML files instead.\n3. **Do not suppress trim warnings without verifying safety.** `[UnconditionalSuppressMessage]` hides warnings but does not fix the underlying issue. Only suppress when you have verified the code is safe (e.g., via ILLink descriptors).\n4. **Do not forget `TrimmerSingleWarn=false` when debugging trim issues.** Without it, you get one summary warning per assembly, making it impossible to find the specific problematic call site.\n5. **Do not confuse `IsTrimmable` with `PublishTrimmed`.** `IsTrimmable` declares a library is trim-safe and enables the analyzer. `PublishTrimmed` enables the linker in applications. They serve different purposes.\n6. **Do not add `[RequiresUnreferencedCode]` to methods that do not use reflection.** The annotation propagates virally -- callers must also be annotated or suppress the warning. Only annotate methods that actually use trim-unsafe reflection.\n\n\n## References\n\n- [Trim self-contained applications](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options)\n- [Prepare libraries for trimming](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/prepare-libraries-for-trimming)\n- [Introduction to trim warnings](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/fixing-warnings)\n- [Trimming annotation attributes](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/prepare-libraries-for-trimming#trimming-annotation-attributes)\n- [ILLink descriptor format](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options#descriptor-format)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":12309,"content_sha256":"ca1ff97de64e8295e3a152902c826a2275e88b3ed903f8e971eb98fb087dc809"},{"filename":"references/version-detection.md","content":"# Version Detection\n\n```bash\ndotnet --version 2>/dev/null\n```\n\nDetects .NET version information from project files and provides version-specific guidance. This skill runs **first** before any .NET development work. All other skills depend on the detected version to adapt their guidance.\n\nCross-cutting skill referenced by [skill:dotnet-advisor] and virtually all specialist skills. See also [skill:dotnet-api] for .NET 10+ file-based apps that run without a `.csproj`.\n\n## Fast Repository Scan (Optional)\n\nFor large repos, run the bundled scanner first to quickly inventory TFM/SDK signals before applying the precedence algorithm below:\n\n```bash\npython3 skills/dotnet-tooling/scripts/scan-dotnet-targets.py --root . --json\n```\n\nUse the script output (`project_target_frameworks`, `global_json.sdk_version`, `workflow_dotnet_versions`) as discovery input. The precedence rules below remain authoritative for final TFM selection.\n\n\n## Detection Precedence Algorithm\n\nRead project files in this strict order. **Higher-numbered sources are lower priority.** Stop falling through once a TFM is resolved.\n\n### 1. Direct `\u003cTargetFramework>` in .csproj (highest priority)\n\nRead the nearest `.csproj` file to the current working file/directory.\n\n```xml\n\u003cPropertyGroup>\n \u003cTargetFramework>net10.0\u003c/TargetFramework>\n\u003c/PropertyGroup>\n```\n\nIf found **and the value is a literal TFM** (e.g., `net10.0`, not `$(SomeProperty)`), this is the authoritative TFM. Report it and proceed to additional detection (Step 5).\n\nIf the value is an MSBuild property expression (starts with `$(`), skip to **Step 4** for unresolved property handling.\n\n### 2. `\u003cTargetFrameworks>` in .csproj (multi-targeting)\n\n```xml\n\u003cPropertyGroup>\n \u003cTargetFrameworks>net8.0;net10.0\u003c/TargetFrameworks>\n\u003c/PropertyGroup>\n```\n\nIf found:\n- Report **all** TFMs (semicolon-delimited)\n- Guide based on the **highest** TFM (e.g., net10.0)\n- Note polyfill needs for lower TFMs: \"Consider `references/multi-targeting.md` for PolySharp/Polyfill to backport language features to net8.0\"\n- Proceed to additional detection (Step 5)\n\n### 3. `Directory.Build.props` shared TFM\n\nIf no `\u003cTargetFramework>` or `\u003cTargetFrameworks>` found in the .csproj (or if the .csproj inherits from shared props), read `Directory.Build.props` in the current directory or any parent directory up to the solution root.\n\n```xml\n\u003c!-- Directory.Build.props -->\n\u003cPropertyGroup>\n \u003cTargetFramework>net10.0\u003c/TargetFramework>\n\u003c/PropertyGroup>\n```\n\nIf found, use this as the TFM. Note: per-project `.csproj` values override `Directory.Build.props`.\n\n### 4. MSBuild Property Expressions (fallback with warning)\n\nIf the TFM value is an MSBuild property expression rather than a literal:\n\n```xml\n\u003cTargetFramework>$(MyCustomTfm)\u003c/TargetFramework>\n```\n\nEmit warning:\n> **Warning: Unresolved MSBuild property `$(MyCustomTfm)`.** Cannot determine TFM statically. Falling back to SDK version from `global.json`.\n\nThen fall through to `global.json` SDK version (Step 4a) or `dotnet --version` (Step 4b).\n\n#### 4a. `global.json` SDK version\n\n```json\n{\n \"sdk\": {\n \"version\": \"10.0.100\"\n }\n}\n```\n\nMap SDK version to approximate TFM:\n- `8.0.xxx` -> net8.0\n- `9.0.xxx` -> net9.0\n- `10.0.xxx` -> net10.0\n- `11.0.xxx-preview.x` -> net11.0\n\nReport: \"Inferred TFM from `global.json` SDK version. Verify actual TFM in project file.\"\n\n#### 4b. `dotnet --version` (last resort)\n\nIf no `global.json` exists, use `dotnet --version` output to infer SDK version. Same mapping as 4a.\n\nReport: \"Inferred TFM from installed SDK version. No `global.json` or `.csproj` found. Consider creating a project with `dotnet new`.\"\n\n\n## Additional Detection (Step 5)\n\nAfter resolving the TFM, also check these files for supplementary version information. Always perform these checks regardless of which precedence step resolved the TFM.\n\n### 5a. `global.json` SDK Version\n\nEven if TFM was resolved from .csproj, read `global.json` for:\n- `sdk.version` -- the pinned SDK version\n- `sdk.rollForward` -- the rollForward policy (e.g., `latestFeature`, `latestPatch`)\n\nReport the SDK version alongside the TFM. Flag inconsistencies:\n> **Warning: TFM `net10.0` but `global.json` pins SDK `9.0.100`.** The project targets a newer framework than the pinned SDK. Update `global.json` or verify the build environment has the correct SDK.\n\n### 5b. C# Language Version\n\nCheck for explicit `\u003cLangVersion>` in .csproj or `Directory.Build.props`:\n\n```xml\n\u003cLangVersion>preview\u003c/LangVersion>\n```\n\n- If `preview` -- report \"C# preview features enabled. Unlocks the next C# version available in the installed SDK (e.g., C# 15 preview features with a .NET 11 preview SDK).\"\n- If `latest` -- report the default C# version for the detected TFM\n- If explicit version (e.g., `12.0`) -- report that version, warn if it's below the TFM default\n- If absent -- use the default C# version for the TFM (see reference data below)\n\n### 5c. Preview Feature Detection\n\nCheck for these properties in .csproj or `Directory.Build.props`:\n\n**EnablePreviewFeatures:**\n```xml\n\u003cEnablePreviewFeatures>true\u003c/EnablePreviewFeatures>\n```\nReport: \".NET preview features enabled. Access to preview APIs and types.\"\n\n**Runtime-async feature flag (.NET 11+):**\n```xml\n\u003cFeatures>$(Features);runtime-async=on\u003c/Features>\n```\nReport: \"Runtime-async enabled. Async/await uses runtime-level execution instead of compiler state machines.\"\n\nNote: runtime-async requires `\u003cEnablePreviewFeatures>true\u003c/EnablePreviewFeatures>` as well.\n\n### 5d. Multi-targeting Details\n\nIf multi-targeting was detected (Step 2), also note:\n- Which TFMs are LTS vs STS vs Preview\n- Which TFMs are approaching end-of-support\n- Suggest `references/multi-targeting.md` for polyfill guidance\n\n\n## Structured Output Format\n\nAfter detection, present results in this structured format:\n\n```\n.NET Version Detection Results\n==============================\nTFM: net10.0 (or net8.0;net10.0 for multi-targeting)\nHighest TFM: net10.0\nC# Version: 14 (default for net10.0)\nSDK Version: 10.0.100 (from global.json)\nPreview Features: none\nRuntime-Async: not enabled\nWarnings: none\n\nGuidance: This project targets .NET 10 LTS with C# 14. Use modern patterns\nincluding field-backed properties, collection expressions, and primary\nconstructors. All guidance will target net10.0 capabilities.\n```\n\nFor multi-targeting:\n\n```\n.NET Version Detection Results\n==============================\nTFMs: net8.0;net10.0\nHighest TFM: net10.0\nC# Version: 14 (default for highest TFM)\nSDK Version: 10.0.100 (from global.json)\nPreview Features: none\nWarnings: net8.0 reaches end of support Nov 2026\n\nGuidance: Multi-targeting net8.0 and net10.0. Guide on net10.0 patterns.\nFor net8.0 compatibility, use PolySharp/Polyfill for language features.\nSee `references/multi-targeting.md` for detailed polyfill guidance.\n```\n\n\n## Edge Cases\n\n### No .csproj Found\nIf no `.csproj` exists in the workspace:\n- Check for `.sln` or `.slnx` files and look for referenced projects\n- If no project files found, continue the fallback chain:\n 1. Read `global.json` for SDK version (Step 4a) and infer TFM from it\n 2. If no `global.json`, use `dotnet --version` (Step 4b) to infer TFM\n 3. Only if both inference methods fail: \"No .NET project or SDK detected. Defaulting guidance to net10.0 (current LTS). Use `dotnet new` to create a project.\"\n\n### MSBuild Property Indirection\nIf `\u003cTargetFramework>` contains `$(PropertyName)`:\n- Emit: \"**Unresolved property: `$(PropertyName)`.** Cannot determine TFM from static analysis.\"\n- Fall back to `global.json` SDK version, then `dotnet --version`\n- Recommend verifying TFM via `dotnet --list-sdks` or `dotnet msbuild -getProperty:TargetFramework`\n\n### Inconsistent Files\nIf .csproj says `net10.0` but `global.json` pins SDK `9.0.100`:\n- Prefer the .csproj TFM (higher precedence)\n- Warn about the mismatch\n- Suggest updating `global.json` to match\n\n### No SDK Installed\nIf `dotnet --version` fails or is not found:\n- Report: \"No .NET SDK detected on this system. Install from https://dot.net\"\n- If project files exist, still report the TFM from project files\n- Note that builds will fail without the SDK\n\n### `.fsproj` and `.vbproj`\nThe same detection logic applies to F# (`.fsproj`) and VB.NET (`.vbproj`) projects. The `\u003cTargetFramework>` element is identical across all .NET project types.\n\n\n## Caching Behavior\n\nVersion detection results should be cached per-project (per .csproj path). Re-detect when:\n- The user switches to a different project within the solution\n- The user explicitly asks to re-detect\n- A `.csproj`, `Directory.Build.props`, or `global.json` file is modified\n\n\n## Version-to-Feature Reference Data\n\n> **Last updated: 2026-02-11**\n\nThis reference data maps .NET versions to their C# language versions, key features, and support lifecycle. This section is **separate from the detection logic above** -- detection determines which version is in use; this data maps that version to available features.\n\n### Version Matrix\n\n| .NET Version | Status | C# Version | TFM | Support End | Notes |\n|-------------|--------|-------------|-----|-------------|-------|\n| .NET 8 | LTS (active) | C# 12 | net8.0 | Nov 2026 | Approaching end of support |\n| .NET 9 | STS | C# 13 | net9.0 | May 2026 | Approaching end of support |\n| .NET 10 | LTS (current) | C# 14 | net10.0 | Nov 2028 | Recommended for new projects |\n| .NET 11 | Preview 1 | C# 15 (preview) | net11.0 | TBD (expected STS end: ~May 2028 if Nov 2026 GA) | Preview only -- not for production |\n\n### C# Version Feature Highlights\n\n**C# 12 (net8.0)**\n- Primary constructors for classes/structs\n- Collection expressions (`[1, 2, 3]`)\n- `ref readonly` parameters\n- Default lambda parameters\n- Alias any type with `using`\n- Inline arrays\n- Interceptors (experimental)\n\n**C# 13 (net9.0)**\n- `params` collections (any collection type)\n- `Lock` type (`System.Threading.Lock`)\n- New escape sequence `\\e`\n- Method group natural type improvements\n- Implicit indexer access in object initializers\n- `ref` and `unsafe` in iterators/async\n- Partial properties\n\n**C# 14 (net10.0)**\n- Field-backed properties (`field` contextual keyword)\n- `nameof` for unbound generic types\n- Extension improvements (extension blocks)\n- First-class `Span\u003cT>` in more contexts\n- `allows ref struct` anti-constraint for generics\n\n**C# 15 preview (net11.0)**\n- Collection expression arguments (`with()` syntax for capacity/comparers):\n ```csharp\n List\u003cint> nums = [with(capacity: 32), 0, ..evens, ..odds];\n HashSet\u003cstring> names = [with(comparer: StringComparer.OrdinalIgnoreCase), \"Alice\"];\n ```\n- Additional features expected as .NET 11 progresses through preview\n\n### .NET 11 Preview 1 Notable Features\n\nThese features are available when `net11.0` TFM is detected with preview features enabled:\n\n- **Runtime-async**: Async/await at runtime level (requires `\u003cEnablePreviewFeatures>true\u003c/EnablePreviewFeatures>` + `\u003cFeatures>$(Features);runtime-async=on\u003c/Features>`)\n- **Zstandard compression**: `System.IO.Compression.Zstandard` (2-7x faster than Brotli/Deflate)\n- **BFloat16**: `System.Numerics.BFloat16` for AI/ML workloads\n- **Happy Eyeballs**: `ConnectAlgorithm.Parallel` for dual-stack networking\n- **CoreCLR for Android**: Default runtime for MAUI Android Release builds\n- **XAML source generation**: Default in MAUI (replaces XAMLC)\n- **EF Core**: Complex types + JSON columns with TPT/TPC inheritance\n- **CoreCLR on WASM**: Experimental alternative to Mono for Blazor WASM\n\n### Support Lifecycle Guidance\n\nWhen reporting version information, include lifecycle context:\n- **End-of-support approaching** (within 6 months): Warn and suggest `references/version-upgrade.md`\n- **Preview/RC**: Warn \"not for production use\" unless user explicitly opted in\n- **STS reaching end**: Note shorter support window compared to LTS\n- **Current LTS**: Confirm as recommended target for new projects\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":11900,"content_sha256":"9655f72d80cfa90c467659213d602fe0800bdc71cf3472e0b61728318bc63144"},{"filename":"references/version-upgrade.md","content":"# Version Upgrade\n\nComprehensive guide for .NET version upgrade planning and execution. This skill consumes the structured output from `references/version-detection.md` (current TFM, SDK version, preview flags) and provides actionable upgrade guidance based on three defined upgrade lanes. Covers TFM migration, package updates, breaking change detection, deprecated API replacement, and test validation.\n\n## Upgrade Lanes\n\nSelect the appropriate upgrade lane based on project requirements and ecosystem constraints.\n\n| Lane | Path | Use Case | Risk Level |\n|------|------|----------|------------|\n| **Production (default)** | net8.0 -> net10.0 | LTS-to-LTS, recommended for most apps | Low -- both endpoints are LTS with long support windows |\n| **Staged production** | net8.0 -> net9.0 -> net10.0 | When ecosystem dependencies require incremental migration | Medium -- intermediate STS version has shorter support |\n| **Experimental** | net10.0 -> net11.0 (preview) | Non-production exploration of upcoming features | High -- preview APIs may change or be removed |\n\n### Lane Selection Decision Flow\n\n1. Are all your NuGet dependencies available on the target LTS? **Yes** -> Production lane (direct LTS-to-LTS).\n2. Do any dependencies require an intermediate version? **Yes** -> Staged production lane.\n3. Are you exploring preview features for R&D or proof-of-concept? **Yes** -> Experimental lane.\n\n\n## Production Lane: LTS-to-LTS (net8.0 -> net10.0)\n\nThe recommended default upgrade path. Both .NET 8 and .NET 10 are Long-Term Support releases, providing a stable migration with well-documented breaking changes.\n\n### Upgrade Checklist\n\n**Step 1: Update TFM in project files**\n\n```xml\n\u003c!-- Before -->\n\u003cPropertyGroup>\n \u003cTargetFramework>net8.0\u003c/TargetFramework>\n\u003c/PropertyGroup>\n\n\u003c!-- After -->\n\u003cPropertyGroup>\n \u003cTargetFramework>net10.0\u003c/TargetFramework>\n\u003c/PropertyGroup>\n```\n\nFor solutions with shared properties:\n\n```xml\n\u003c!-- Directory.Build.props -->\n\u003cPropertyGroup>\n \u003cTargetFramework>net10.0\u003c/TargetFramework>\n\u003c/PropertyGroup>\n```\n\n**Step 2: Update global.json SDK version**\n\n```json\n{\n \"sdk\": {\n \"version\": \"10.0.100\",\n \"rollForward\": \"latestFeature\"\n }\n}\n```\n\n**Step 3: Update NuGet packages**\n\nUse `dotnet-outdated` to detect stale packages and identify which packages need updates for TFM compatibility:\n\n```bash\n# Install dotnet-outdated as a global tool\ndotnet tool install -g dotnet-outdated-tool\n\n# Check for outdated packages across the solution\ndotnet outdated\n\n# Check a specific project\ndotnet outdated MyProject/MyProject.csproj\n\n# Auto-upgrade to latest stable versions\ndotnet outdated --upgrade\n\n# Upgrade only to the latest minor/patch (safer)\ndotnet outdated --upgrade --version-lock major\n```\n\nFor ASP.NET Core shared framework packages, update version references to match the target TFM:\n\n```xml\n\u003cItemGroup>\n \u003c!-- Match package version to TFM major version -->\n \u003cPackageReference Include=\"Microsoft.AspNetCore.Mvc.Testing\" Version=\"10.*\" />\n \u003cPackageReference Include=\"Microsoft.Extensions.Hosting\" Version=\"10.*\" />\n\u003c/ItemGroup>\n```\n\n**Step 4: Review breaking changes**\n\n```bash\n# Build to surface warnings and errors\ndotnet build --warnaserror\n\n# Run analyzers for deprecated API usage\ndotnet build /p:TreatWarningsAsErrors=true\n```\n\nReview the official breaking change lists:\n- [.NET 9 Breaking Changes](https://learn.microsoft.com/en-us/dotnet/core/compatibility/9.0)\n- [.NET 10 Breaking Changes](https://learn.microsoft.com/en-us/dotnet/core/compatibility/10.0)\n\n**Step 5: Replace deprecated APIs**\n\nCommon replacements when moving from net8.0 to net10.0:\n\n| Deprecated | Replacement | Notes |\n|-----------|-------------|-------|\n| `BinaryFormatter` | `System.Text.Json` or `MessagePack` | `BinaryFormatter` throws `PlatformNotSupportedException` starting in net9.0 |\n| `Thread.Abort()` | Cooperative cancellation via `CancellationToken` | `Thread.Abort()` throws `PlatformNotSupportedException` |\n| `WebRequest` / `HttpWebRequest` | `HttpClient` via `IHttpClientFactory` | Obsolete (`SYSLIB0014`), migrate to `HttpClient` |\n\n**Recommended modernizations** (not deprecated, but improve performance and AOT readiness):\n\n| Pattern | Improvement | Notes |\n|---------|-------------|-------|\n| `Regex` without source gen | `[GeneratedRegex]` attribute | Source-generated regex is faster and AOT-compatible |\n\n**Step 6: Run tests and validate**\n\n```bash\n# Run full test suite\ndotnet test --configuration Release\n\n# Enable trim/AOT analyzers to surface compatibility warnings without publishing\ndotnet build --configuration Release /p:EnableTrimAnalyzer=true /p:EnableAotAnalyzer=true\n```\n\n### .NET Upgrade Assistant\n\nThe .NET Upgrade Assistant automates parts of the migration process. It is most useful for large solutions with many projects.\n\n```bash\n# Install as a global tool\ndotnet tool install -g upgrade-assistant\n\n# Analyze a project (non-destructive, reports recommendations)\nupgrade-assistant analyze MyProject/MyProject.csproj\n\n# Perform the upgrade (modifies files)\nupgrade-assistant upgrade MyProject/MyProject.csproj\n```\n\n**When to use Upgrade Assistant:**\n- Large solutions with many projects and complex dependency graphs\n- Legacy .NET Framework-to-modern-.NET migrations\n- When you need a comprehensive dependency analysis before committing to an upgrade\n\n**Limitations:**\n- May not handle all breaking changes -- manual review is still required\n- Custom MSBuild extensions and third-party build tooling may need manual adjustment\n- Does not update runtime behavior differences -- test coverage is essential\n- Not needed for small projects with few dependencies (manual TFM update is simpler)\n\n\n## Staged Production Lane: net8.0 -> net9.0 -> net10.0\n\nUse the staged lane when direct LTS-to-LTS migration is blocked by ecosystem constraints. Staging through .NET 9 (STS) provides an incremental migration path.\n\n### When to Stage Through .NET 9\n\n- **Third-party package compatibility:** A critical dependency only supports net9.0 (not yet net10.0) and you need to upgrade away from net8.0 now.\n- **Large breaking change surface:** The combined breaking changes from net8.0 to net10.0 are too many to address at once; incremental steps reduce risk.\n- **Incremental validation:** You want to validate behavior changes at each step before proceeding.\n\n### .NET 9 Context\n\n.NET 9 is a Standard Term Support (STS) release:\n- **GA:** November 2024\n- **End of support:** May 2026 (18 months from GA)\n- **C# version:** C# 13\n\nBecause .NET 9 is approaching end of support, do not stop at net9.0. Plan the second hop (net9.0 -> net10.0) before starting the first.\n\n### Staged Upgrade Checklist\n\n**Hop 1: net8.0 -> net9.0**\n\n1. Update TFM to `net9.0` in .csproj / `Directory.Build.props`\n2. Update `global.json` to SDK `9.0.xxx`\n3. Run `dotnet outdated --upgrade` for package updates\n4. Review [.NET 9 breaking changes](https://learn.microsoft.com/en-us/dotnet/core/compatibility/9.0)\n5. Replace deprecated APIs flagged by `SYSLIB` diagnostics and `CS0618` warnings (e.g., `BinaryFormatter` -> `System.Text.Json`)\n6. Run `dotnet test --configuration Release` to validate\n7. Deploy to staging environment, validate in production with monitoring\n\n**Hop 2: net9.0 -> net10.0**\n\n1. Update TFM to `net10.0` in .csproj / `Directory.Build.props`\n2. Update `global.json` to SDK `10.0.xxx`\n3. Run `dotnet outdated --upgrade` again\n4. Review [.NET 10 breaking changes](https://learn.microsoft.com/en-us/dotnet/core/compatibility/10.0)\n5. Replace any additional deprecated APIs introduced between net9.0 and net10.0\n6. Run `dotnet test --configuration Release` to validate\n7. Deploy to staging, remove any net9.0-specific workarounds\n\n**Timeline guidance:** Complete both hops within the .NET 9 support window (before May 2026). Running production workloads on an unsupported STS release exposes you to unpatched security vulnerabilities.\n\n\n## Experimental Lane: net10.0 -> net11.0 (Preview)\n\nFor non-production exploration of upcoming features. .NET 11 is currently in preview and its APIs may change or be removed before GA.\n\n### Guardrails\n\n- **Non-production only.** Do not deploy preview-targeted code to production environments.\n- **Separate branch or project.** Isolate experimental work from production codebases.\n- **Pin the preview SDK.** Use `global.json` to lock the specific preview SDK version to avoid silent behavior changes between previews.\n- **Expect breaking changes between previews.** APIs marked `[RequiresPreviewFeatures]` can change shape between preview releases.\n\n### Enabling Preview Features\n\n**Step 1: Install the preview SDK**\n\n```bash\n# Verify installed SDKs\ndotnet --list-sdks\n\n# Example: 11.0.100-preview.1.xxxxx should appear\n```\n\n**Step 2: Pin to preview SDK in global.json**\n\n```json\n{\n \"sdk\": {\n \"version\": \"11.0.100-preview.1.25120.13\",\n \"rollForward\": \"disable\"\n }\n}\n```\n\nUse `\"rollForward\": \"disable\"` to prevent automatic SDK version advancement between previews.\n\n**Step 3: Set TFM and enable preview features**\n\n```xml\n\u003cPropertyGroup>\n \u003cTargetFramework>net11.0\u003c/TargetFramework>\n \u003cLangVersion>preview\u003c/LangVersion>\n \u003cEnablePreviewFeatures>true\u003c/EnablePreviewFeatures>\n\u003c/PropertyGroup>\n```\n\n**Step 4: Enable runtime-async (optional)**\n\nRuntime-async moves async/await from compiler-generated state machines to runtime-level execution, reducing allocations and improving performance for async-heavy workloads:\n\n```xml\n\u003cPropertyGroup>\n \u003cTargetFramework>net11.0\u003c/TargetFramework>\n \u003cLangVersion>preview\u003c/LangVersion>\n \u003cEnablePreviewFeatures>true\u003c/EnablePreviewFeatures>\n \u003cFeatures>$(Features);runtime-async=on\u003c/Features>\n\u003c/PropertyGroup>\n```\n\nRuntime-async requires both `EnablePreviewFeatures` and the `Features` flag. It is experimental and may change significantly before GA.\n\n### Experimental Upgrade Checklist\n\n1. Install the .NET 11 preview SDK and pin it in `global.json` with `\"rollForward\": \"disable\"`\n2. Update TFM to `net11.0` and set `\u003cLangVersion>preview\u003c/LangVersion>` + `\u003cEnablePreviewFeatures>true\u003c/EnablePreviewFeatures>`\n3. Run `dotnet outdated` to check package compatibility with the preview TFM\n4. Build and review warnings -- preview SDKs may flag new `SYSLIB` diagnostics or `CA2252` (preview feature usage)\n5. Replace any deprecated APIs surfaced by the new analyzer version\n6. Run `dotnet test` to validate -- expect some third-party packages to lack preview TFM support\n7. Document findings for future production upgrade planning\n\n### What to Explore in .NET 11 Preview\n\n| Feature | Area | Notes |\n|---------|------|-------|\n| Runtime-async | Performance | Async/await at runtime level; requires opt-in |\n| Zstandard compression | I/O | `System.IO.Compression.Zstandard`; 2-7x faster than Brotli |\n| BFloat16 | Numerics | `System.Numerics.BFloat16` for AI/ML workloads |\n| Happy Eyeballs | Networking | `ConnectAlgorithm.Parallel` for dual-stack IPv4/IPv6 |\n| C# 15 preview features | Language | Collection expression arguments (`with()` syntax) |\n| CoreCLR on WASM | Runtime | Experimental alternative to Mono for Blazor WASM |\n\n\n## Breaking Change Detection\n\nSystematic approaches to identify and resolve breaking changes during any upgrade.\n\n### Build-Time Detection\n\n```bash\n# Clean build to surface all warnings (not just incremental)\ndotnet clean && dotnet build --no-incremental\n\n# Treat warnings as errors to catch deprecation notices\ndotnet build /p:TreatWarningsAsErrors=true\n\n# List specific obsolete API warnings\ndotnet build 2>&1 | grep -E \"CS0618|CS0612\"\n```\n\n- `CS0618`: Use of an `[Obsolete]` member with a message\n- `CS0612`: Use of an `[Obsolete]` member without a message\n- `CS8073`: Expression always evaluates to the same value (type behavior change)\n\n### Analyzer Diagnostics\n\nEnable .NET analyzers to detect additional issues:\n\n```xml\n\u003cPropertyGroup>\n \u003cEnableNETAnalyzers>true\u003c/EnableNETAnalyzers>\n \u003cAnalysisLevel>latest-recommended\u003c/AnalysisLevel>\n\u003c/PropertyGroup>\n```\n\nKey analyzer categories for upgrades:\n- `CA1422`: Platform compatibility (API removed on target platform)\n- `CA1416`: Platform-specific API usage without guards\n- `CA2252`: Opting into preview features\n- `SYSLIB0XXX`: Obsolete system API diagnostics (e.g., `SYSLIB0011` for BinaryFormatter)\n\n### API Diff Tools\n\nFor library authors, use API compatibility tools to validate public surface changes:\n\n```bash\n# Package validation detects breaking changes against a baseline\ndotnet pack /p:EnablePackageValidation=true /p:PackageValidationBaselineVersion=1.0.0\n\n# Standalone API comparison\ndotnet tool install -g Microsoft.DotNet.ApiCompat.Tool\napicompat --left-assembly bin/Release/net8.0/MyLib.dll \\\n --right-assembly bin/Release/net10.0/MyLib.dll\n```\n\nSee `references/multi-targeting.md` for detailed API compatibility validation workflows including suppression files and CI integration.\n\n\n## Package Update Strategies\n\n### dotnet-outdated\n\nThe `dotnet-outdated` tool provides a comprehensive view of package staleness:\n\n```bash\n# Install\ndotnet tool install -g dotnet-outdated-tool\n\n# Show all outdated packages with current vs latest versions\ndotnet outdated\n\n# Lock major version, show only minor/patch updates (safer incremental upgrades)\ndotnet outdated --version-lock major\n\n# Auto-upgrade with version locking to avoid unexpected major bumps\ndotnet outdated --upgrade --version-lock major\n\n# Output as JSON for CI integration\ndotnet outdated --output-format json\n```\n\n### Central Package Management\n\nFor solutions using Central Package Management (`Directory.Packages.props`), update versions centrally:\n\n```xml\n\u003c!-- Directory.Packages.props -->\n\u003cProject>\n \u003cPropertyGroup>\n \u003cManagePackageVersionsCentrally>true\u003c/ManagePackageVersionsCentrally>\n \u003c/PropertyGroup>\n\n \u003cItemGroup>\n \u003c!-- Update all Microsoft.Extensions.* packages to match TFM -->\n \u003cPackageVersion Include=\"Microsoft.Extensions.Hosting\" Version=\"10.*\" />\n \u003cPackageVersion Include=\"Microsoft.Extensions.Http\" Version=\"10.*\" />\n \u003cPackageVersion Include=\"Microsoft.Extensions.Logging\" Version=\"10.*\" />\n \u003c!-- Third-party packages: check compatibility before upgrading -->\n \u003cPackageVersion Include=\"Serilog\" Version=\"4.*\" />\n \u003c/ItemGroup>\n\u003c/Project>\n```\n\n`Directory.Packages.props` resolves hierarchically upward from the project directory. In monorepo structures, verify that nested `Directory.Packages.props` files are not shadowing the root-level configuration.\n\n### ASP.NET Core Shared Framework Packages\n\nASP.NET Core shared framework packages must align their major version with the target TFM. Two valid approaches:\n\n```xml\n\u003cItemGroup>\n \u003c!-- Option A: floating version — auto-resolves latest patch, convenient for upgrades -->\n \u003cPackageReference Include=\"Microsoft.AspNetCore.Mvc.Testing\" Version=\"10.*\" />\n\n \u003c!-- Option B: pinned version — deterministic CI builds, update explicitly -->\n \u003cPackageReference Include=\"Microsoft.AspNetCore.Mvc.Testing\" Version=\"10.0.1\" />\n\u003c/ItemGroup>\n```\n\nPinned versions are recommended for deterministic CI; floating versions are useful during exploratory upgrades. Either way, the **major version must match the TFM** (e.g., `10.x` for `net10.0`).\n\n\n## Agent Gotchas\n\n1. **Do not skip .NET 9 without validating ecosystem compatibility.** While LTS-to-LTS (net8.0 -> net10.0) is the recommended default, some third-party packages may only support net9.0 as an intermediate step. Check package compatibility before selecting a lane.\n\n2. **Do not run production workloads on preview SDKs.** .NET 11 preview APIs are unstable and will change between preview releases. Isolate experimental work in separate branches or projects with pinned preview SDK versions.\n\n3. **Do not assume .NET 9 STS has 12-month support.** STS lifecycle is 18 months from GA. .NET 9 GA was November 2024, so end-of-support is May 2026. Always calculate from actual GA date, not release year.\n\n4. **Ensure ASP.NET shared framework package major versions match the TFM.** Packages like `Microsoft.AspNetCore.Mvc.Testing` must have their major version aligned with the project TFM (e.g., `10.x` for `net10.0`). Pin exact versions for deterministic CI or float with wildcards (e.g., `10.*`) during exploratory upgrades.\n\n5. **Do not re-implement TFM detection.** This skill consumes the structured output from `references/version-detection.md`. Never parse `.csproj` files to determine the current version -- use the detection skill's output (TFM, C# version, SDK version, warnings).\n\n6. **Do not treat `dotnet-outdated --upgrade` as a complete solution.** It updates package versions but does not handle breaking API changes within those packages. Always build, test, and review changelogs after upgrading packages.\n\n7. **Do not use `\"rollForward\": \"latestMajor\"` with preview SDKs.** This can silently advance to a different preview version with breaking changes. Use `\"rollForward\": \"disable\"` for preview SDKs to maintain reproducible builds.\n\n8. **Do not forget `Directory.Packages.props` hierarchy.** In monorepo structures, nested `Directory.Packages.props` files shadow parent-level configurations. When upgrading, search upward from the project directory to find all `Directory.Packages.props` files that may affect package resolution.\n\n9. **Do not ignore `SYSLIB` diagnostic codes during upgrade.** These system-level obsolete warnings (e.g., `SYSLIB0011` for BinaryFormatter, `SYSLIB0014` for WebRequest) indicate APIs that will throw at runtime on newer TFMs, not just compile-time warnings.\n\n\n## Prerequisites\n\n- .NET SDK for the target TFM installed (e.g., .NET 10 SDK for net10.0 upgrade)\n- `dotnet-outdated-tool` (for package staleness detection): `dotnet tool install -g dotnet-outdated-tool`\n- `upgrade-assistant` (optional, for automated migration): `dotnet tool install -g upgrade-assistant`\n- Output from `references/version-detection.md` (current TFM, SDK version, preview flags)\n\n\n## References\n\n> **Last verified: 2026-02-12**\n\n- [.NET Support Policy and Lifecycle](https://dotnet.microsoft.com/en-us/platform/support/policy)\n- [.NET 9 Breaking Changes](https://learn.microsoft.com/en-us/dotnet/core/compatibility/9.0)\n- [.NET 10 Breaking Changes](https://learn.microsoft.com/en-us/dotnet/core/compatibility/10.0)\n- [.NET Upgrade Assistant](https://dotnet.microsoft.com/en-us/platform/upgrade-assistant)\n- [dotnet-outdated Tool](https://github.com/dotnet-outdated/dotnet-outdated)\n- [Central Package Management](https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management)\n- [Target Framework Monikers](https://learn.microsoft.com/en-us/dotnet/standard/frameworks)\n- [.NET Analyzers Overview](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview)\n- [Package Validation](https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/overview)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":18750,"content_sha256":"afc72f8af7afc067c11594e57440f4f12448bf59c4f6826855816ca8abbb4d2e"},{"filename":"references/vscode-debug.md","content":"# VS Code Debug Configuration\n\nConfigure VS Code for .NET debugging with `launch.json` and `tasks.json`. Covers `coreclr` launch/attach configurations, multi-project debugging, environment variables, hot reload with `dotnet watch`, and common debugging scenarios. Applicable to any agent that creates or modifies `.vscode/` configuration files.\n\n## Quick Setup\n\nFor a standard .NET project, create `.vscode/launch.json` and `.vscode/tasks.json`:\n\n```bash\nmkdir -p .vscode\n```\n\n## tasks.json\n\nBuild tasks provide the `preLaunchTask` that compiles the project before debugging.\n\n```json\n{\n \"version\": \"2.0.0\",\n \"tasks\": [\n {\n \"label\": \"build\",\n \"command\": \"dotnet\",\n \"type\": \"process\",\n \"args\": [\n \"build\",\n \"${workspaceFolder}/src/MyApp/MyApp.csproj\",\n \"--configuration\", \"Debug\",\n \"--no-restore\"\n ],\n \"problemMatcher\": \"$msCompile\",\n \"group\": {\n \"kind\": \"build\",\n \"isDefault\": true\n }\n },\n {\n \"label\": \"build-solution\",\n \"command\": \"dotnet\",\n \"type\": \"process\",\n \"args\": [\n \"build\",\n \"${workspaceFolder}/MyApp.sln\",\n \"--configuration\", \"Debug\"\n ],\n \"problemMatcher\": \"$msCompile\"\n },\n {\n \"label\": \"watch\",\n \"command\": \"dotnet\",\n \"type\": \"process\",\n \"args\": [\n \"watch\",\n \"run\",\n \"--project\", \"${workspaceFolder}/src/MyApp/MyApp.csproj\"\n ],\n \"problemMatcher\": \"$msCompile\",\n \"isBackground\": true\n }\n ]\n}\n```\n\n## launch.json\n\n### Launch a Console App\n\n```json\n{\n \"version\": \"0.2.0\",\n \"configurations\": [\n {\n \"name\": \"Launch Console App\",\n \"type\": \"coreclr\",\n \"request\": \"launch\",\n \"preLaunchTask\": \"build\",\n \"program\": \"${workspaceFolder}/src/MyApp/bin/Debug/net10.0/MyApp.dll\",\n \"args\": [],\n \"cwd\": \"${workspaceFolder}/src/MyApp\",\n \"console\": \"integratedTerminal\",\n \"stopAtEntry\": false\n }\n ]\n}\n```\n\n### Launch an ASP.NET Core App\n\n```json\n{\n \"name\": \"Launch Web API\",\n \"type\": \"coreclr\",\n \"request\": \"launch\",\n \"preLaunchTask\": \"build\",\n \"program\": \"${workspaceFolder}/src/MyApi/bin/Debug/net10.0/MyApi.dll\",\n \"args\": [],\n \"cwd\": \"${workspaceFolder}/src/MyApi\",\n \"stopAtEntry\": false,\n \"env\": {\n \"ASPNETCORE_ENVIRONMENT\": \"Development\",\n \"ASPNETCORE_URLS\": \"https://localhost:5001;http://localhost:5000\"\n },\n \"serverReadyAction\": {\n \"action\": \"openExternally\",\n \"pattern\": \"\\\\bNow listening on:\\\\s+(https?://\\\\S+)\"\n }\n}\n```\n\n### Attach to a Running Process\n\n```json\n{\n \"name\": \"Attach to Process\",\n \"type\": \"coreclr\",\n \"request\": \"attach\",\n \"processId\": \"${command:pickProcess}\"\n}\n```\n\n### Launch with Command-Line Arguments\n\n```json\n{\n \"name\": \"Launch with Args\",\n \"type\": \"coreclr\",\n \"request\": \"launch\",\n \"preLaunchTask\": \"build\",\n \"program\": \"${workspaceFolder}/src/MyCli/bin/Debug/net10.0/MyCli.dll\",\n \"args\": [\"--input\", \"data.csv\", \"--verbose\"],\n \"cwd\": \"${workspaceFolder}\",\n \"console\": \"integratedTerminal\"\n}\n```\n\n### Launch Tests with Debugger\n\n```json\n{\n \"name\": \"Debug Tests\",\n \"type\": \"coreclr\",\n \"request\": \"launch\",\n \"preLaunchTask\": \"build\",\n \"program\": \"dotnet\",\n \"args\": [\n \"test\",\n \"${workspaceFolder}/tests/MyApp.Tests/MyApp.Tests.csproj\",\n \"--no-build\",\n \"--filter\", \"FullyQualifiedName~MyTestClass\"\n ],\n \"cwd\": \"${workspaceFolder}\",\n \"console\": \"integratedTerminal\"\n}\n```\n\n## Multi-Project Debugging\n\n### Compound Launch (Multiple Projects Simultaneously)\n\nDebug an API and a worker service together:\n\n```json\n{\n \"version\": \"0.2.0\",\n \"configurations\": [\n {\n \"name\": \"Launch API\",\n \"type\": \"coreclr\",\n \"request\": \"launch\",\n \"preLaunchTask\": \"build-solution\",\n \"program\": \"${workspaceFolder}/src/MyApi/bin/Debug/net10.0/MyApi.dll\",\n \"cwd\": \"${workspaceFolder}/src/MyApi\",\n \"env\": { \"ASPNETCORE_ENVIRONMENT\": \"Development\" }\n },\n {\n \"name\": \"Launch Worker\",\n \"type\": \"coreclr\",\n \"request\": \"launch\",\n \"preLaunchTask\": \"build-solution\",\n \"program\": \"${workspaceFolder}/src/MyWorker/bin/Debug/net10.0/MyWorker.dll\",\n \"cwd\": \"${workspaceFolder}/src/MyWorker\"\n }\n ],\n \"compounds\": [\n {\n \"name\": \"API + Worker\",\n \"configurations\": [\"Launch API\", \"Launch Worker\"],\n \"stopAll\": true\n }\n ]\n}\n```\n\n## Environment Variables\n\n```json\n{\n \"name\": \"Launch with Environment\",\n \"type\": \"coreclr\",\n \"request\": \"launch\",\n \"preLaunchTask\": \"build\",\n \"program\": \"${workspaceFolder}/src/MyApi/bin/Debug/net10.0/MyApi.dll\",\n \"cwd\": \"${workspaceFolder}/src/MyApi\",\n \"env\": {\n \"ASPNETCORE_ENVIRONMENT\": \"Development\",\n \"ConnectionStrings__DefaultConnection\": \"Host=localhost;Database=mydb\",\n \"Logging__LogLevel__Default\": \"Debug\",\n \"DOTNET_ENVIRONMENT\": \"Development\"\n },\n \"envFile\": \"${workspaceFolder}/.env\"\n}\n```\n\nThe `envFile` property loads variables from a `.env` file (one `KEY=VALUE` per line). Variables in `env` override those from `envFile`.\n\n## Hot Reload with dotnet watch\n\nFor iterative development, use `dotnet watch` instead of the debugger:\n\n```json\n{\n \"label\": \"watch-api\",\n \"command\": \"dotnet\",\n \"type\": \"process\",\n \"args\": [\n \"watch\",\n \"run\",\n \"--project\", \"${workspaceFolder}/src/MyApi/MyApi.csproj\",\n \"--launch-profile\", \"Development\"\n ],\n \"problemMatcher\": \"$msCompile\",\n \"isBackground\": true\n}\n```\n\nTo debug with hot reload, launch `dotnet watch` and then attach:\n\n1. Start `dotnet watch run` in a terminal\n2. Use the \"Attach to Process\" configuration to attach the debugger\n3. Code changes trigger automatic rebuild and restart; reattach after restart\n\n## Remote Debugging\n\n### Docker Container\n\nDebug a .NET app running inside a Docker container by attaching `vsdbg` (the VS Code .NET debugger engine).\n\n**Dockerfile setup** (include debugger in dev image):\n\n```dockerfile\nFROM mcr.microsoft.com/dotnet/sdk:10.0 AS build\nWORKDIR /src\nCOPY . .\nRUN dotnet publish -c Debug -o /app\n\nFROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime\n# Install vsdbg for remote debugging\nRUN apt-get update && apt-get install -y --no-install-recommends curl unzip \\\n && curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg \\\n && apt-get remove -y curl unzip && rm -rf /var/lib/apt/lists/*\nWORKDIR /app\nCOPY --from=build /app .\nENTRYPOINT [\"dotnet\", \"MyApi.dll\"]\n```\n\n**launch.json** for Docker attach:\n\n```json\n{\n \"name\": \"Docker Attach\",\n \"type\": \"coreclr\",\n \"request\": \"attach\",\n \"processId\": \"1\",\n \"pipeTransport\": {\n \"pipeProgram\": \"docker\",\n \"pipeArgs\": [\"exec\", \"-i\", \"my-container-name\"],\n \"debuggerPath\": \"/vsdbg/vsdbg\",\n \"pipeCwd\": \"${workspaceFolder}\"\n },\n \"sourceFileMap\": {\n \"/src\": \"${workspaceFolder}\"\n }\n}\n```\n\n### SSH Remote\n\nDebug a .NET app on a remote Linux server via SSH:\n\n```json\n{\n \"name\": \"SSH Remote Attach\",\n \"type\": \"coreclr\",\n \"request\": \"attach\",\n \"processId\": \"${command:pickRemoteProcess}\",\n \"pipeTransport\": {\n \"pipeProgram\": \"ssh\",\n \"pipeArgs\": [\"-T\", \"user@remote-host\"],\n \"debuggerPath\": \"~/.vsdbg/vsdbg\",\n \"pipeCwd\": \"${workspaceFolder}\"\n },\n \"sourceFileMap\": {\n \"/home/user/app\": \"${workspaceFolder}/src/MyApp\"\n }\n}\n```\n\nInstall vsdbg on the remote host first:\n\n```bash\nssh user@remote-host 'curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l ~/.vsdbg'\n```\n\n### WSL (Windows Subsystem for Linux)\n\nDebug .NET apps running in WSL from VS Code on Windows:\n\n```json\n{\n \"name\": \"WSL Attach\",\n \"type\": \"coreclr\",\n \"request\": \"attach\",\n \"processId\": \"${command:pickProcess}\",\n \"pipeTransport\": {\n \"pipeProgram\": \"wsl\",\n \"pipeArgs\": [],\n \"debuggerPath\": \"~/.vsdbg/vsdbg\",\n \"pipeCwd\": \"${workspaceFolder}\"\n },\n \"sourceFileMap\": {\n \"/mnt/c\": \"C:\\\\\"\n }\n}\n```\n\nAlternatively, open the WSL folder directly in VS Code (`code .` from WSL terminal) for a simpler experience without `pipeTransport`.\n\n### Kubernetes Pod\n\nDebug a .NET app in a Kubernetes pod by exec-ing into it:\n\n```json\n{\n \"name\": \"K8s Pod Attach\",\n \"type\": \"coreclr\",\n \"request\": \"attach\",\n \"processId\": \"1\",\n \"pipeTransport\": {\n \"pipeProgram\": \"kubectl\",\n \"pipeArgs\": [\"exec\", \"-i\", \"my-pod-name\", \"--\"],\n \"debuggerPath\": \"/vsdbg/vsdbg\",\n \"pipeCwd\": \"${workspaceFolder}\"\n },\n \"sourceFileMap\": {\n \"/src\": \"${workspaceFolder}\"\n }\n}\n```\n\nThe pod's container image must include vsdbg. Use a debug-tagged image for dev/staging, not production.\n\n### MAUI / Mobile Device\n\nMAUI debugging uses platform-specific debugger configurations:\n\n| Platform | Debugger | Setup |\n|----------|----------|-------|\n| Android emulator | `coreclr` + ADB | VS Code with MAUI extension; `adb` for device bridge |\n| iOS simulator (macOS) | `coreclr` + Mono | Pair to Mac from VS, or VS Code on macOS directly |\n| Windows (WinUI) | `coreclr` | Standard launch config, target `net10.0-windows10.0.19041.0` |\n\nMAUI debugging in VS Code requires the **.NET MAUI extension** (`ms-dotnettools.dotnet-maui`). For full device debugging, Visual Studio or JetBrains Rider provide a more complete experience.\n\n### Remote Debugging Checklist\n\n1. **Install vsdbg on the target** — `curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg`\n2. **Build with Debug configuration** — Release builds optimize away variables and inline methods\n3. **Map source paths** — use `sourceFileMap` when container/remote paths differ from local paths\n4. **Open firewall ports** if using TCP transport instead of pipe transport\n5. **Don't ship vsdbg in production images** — use multi-stage builds or separate debug images\n\n## Configuration Reference\n\n### Key launch.json Properties\n\n| Property | Values | Purpose |\n|----------|--------|---------|\n| `type` | `coreclr` | .NET debugger (managed code) |\n| `request` | `launch`, `attach` | Start new process or attach to existing |\n| `program` | path to `.dll` | The assembly to debug |\n| `preLaunchTask` | task label | Build before launching |\n| `cwd` | path | Working directory for the launched process |\n| `console` | `integratedTerminal`, `externalTerminal`, `internalConsole` | Where stdout/stderr appear |\n| `stopAtEntry` | `true`, `false` | Break on first line of user code |\n| `args` | string array | Command-line arguments |\n| `env` | object | Environment variable overrides |\n| `envFile` | path | Load env vars from file |\n| `serverReadyAction` | object | Open browser when server starts |\n| `justMyCode` | `true`, `false` | Skip framework/library code in debugger |\n| `symbolOptions` | object | Configure symbol loading |\n| `logging` | object | Enable debugger diagnostic logging |\n| `sourceFileMap` | object | Map source paths for remote debugging |\n\n### Program Path Patterns\n\n```json\n// Explicit path (most reliable)\n\"program\": \"${workspaceFolder}/src/MyApp/bin/Debug/net10.0/MyApp.dll\"\n\n// Dynamic TFM (if only one TFM exists)\n\"program\": \"${workspaceFolder}/src/MyApp/bin/Debug/${input:tfm}/MyApp.dll\"\n```\n\nFor the `program` path, the TFM folder (`net10.0`, `net9.0`, etc.) must match the project's `TargetFramework`. Check the `.csproj` to determine the correct value.\n\n## Agent Guidelines\n\nWhen an agent creates or modifies `.vscode/launch.json` or `.vscode/tasks.json`:\n\n1. **Read the .csproj first** to determine `TargetFramework` and assembly name\n2. **Use the correct TFM** in the `program` path — don't hardcode `net8.0` without checking\n3. **Set `preLaunchTask`** to a build task — launching without building leads to stale binaries\n4. **Use `integratedTerminal`** for console apps that need stdin/stdout interaction\n5. **Add `ASPNETCORE_ENVIRONMENT`** for web projects — without it, the app runs in Production mode\n6. **Prefer `envFile`** over inline `env` for secrets — `.env` files should be in `.gitignore`\n7. **Don't overwrite existing configurations** — read the file first and add/merge configurations\n8. **Use `serverReadyAction`** for web apps — it tells the editor to open the browser when the server is ready\n\n## Troubleshooting\n\n| Problem | Fix |\n|---------|-----|\n| \"Could not find coreclr\" | Install C# extension (ms-dotnettools.csharp) or C# Dev Kit |\n| Program path not found | Check `TargetFramework` in .csproj matches the path TFM folder |\n| Breakpoints not hit | Ensure `--configuration Debug` (not Release); check `justMyCode` setting |\n| Port already in use | Change `ASPNETCORE_URLS` port or kill the existing process |\n| Source maps don't match | Set `sourceFileMap` to map container/remote paths to local paths |\n| Hot reload not working | Ensure `dotnet watch` supports the project type; attach debugger after watch starts |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":13350,"content_sha256":"276d8d8797bcddee7aab8581755cede4eb2e2eb57ccf8ac90d084364ad6f43f7"},{"filename":"scripts/scan-dotnet-targets.py","content":"#!/usr/bin/env python3\n\"\"\"Scan a repository for current .NET targeting signals.\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport re\nimport sys\nfrom pathlib import Path\n\nTARGET_FRAMEWORK_RE = re.compile(\n r\"\u003cTargetFramework>\\s*([^\u003c]+?)\\s*\u003c/TargetFramework>\", re.IGNORECASE\n)\nTARGET_FRAMEWORKS_RE = re.compile(\n r\"\u003cTargetFrameworks>\\s*([^\u003c]+?)\\s*\u003c/TargetFrameworks>\", re.IGNORECASE\n)\nTARGET_FRAMEWORK_VERSION_RE = re.compile(\n r\"\u003cTargetFrameworkVersion>\\s*([^\u003c]+?)\\s*\u003c/TargetFrameworkVersion>\", re.IGNORECASE\n)\nFILE_BASED_TFM_RE = re.compile(\n r\"^\\s*#:\\s*property\\s+TargetFramework\\s*=\\s*([^\\s]+)\", re.IGNORECASE | re.MULTILINE\n)\nDOTNET_VERSION_RE = re.compile(\n r\"dotnet-version\\s*:\\s*['\\\"]?([^'\\\"\\n#]+)\", re.IGNORECASE\n)\nNET_TFM_VERSION_RE = re.compile(r\"^net(\\d+)\\.(\\d+)\")\nSDK_VERSION_RE = re.compile(r\"^(\\d+)(?:\\.(\\d+))?\")\n\n\ndef read_text(path: Path) -> str | None:\n try:\n return path.read_text(encoding=\"utf-8\")\n except (OSError, UnicodeDecodeError):\n return None\n\n\ndef rel_path(path: Path, root: Path) -> str:\n try:\n return str(path.relative_to(root))\n except ValueError:\n return str(path)\n\n\ndef add_evidence(\n store: dict[str, set[str]], value: str, file_path: Path, root: Path\n) -> None:\n normalized = value.strip()\n if not normalized:\n return\n store.setdefault(normalized, set()).add(rel_path(file_path, root))\n\n\ndef split_tfms(raw: str) -> list[str]:\n return [item.strip() for item in raw.split(\";\") if item.strip()]\n\n\ndef collect_project_tfms(root: Path) -> dict[str, set[str]]:\n evidence: dict[str, set[str]] = {}\n patterns = [\n \"**/*.csproj\",\n \"**/*.fsproj\",\n \"**/*.vbproj\",\n \"**/Directory.Build.props\",\n \"**/Directory.Build.targets\",\n ]\n\n files: set[Path] = set()\n for pattern in patterns:\n files.update(root.glob(pattern))\n\n for file_path in sorted(files):\n text = read_text(file_path)\n if text is None:\n continue\n\n for raw in TARGET_FRAMEWORK_RE.findall(text):\n for tfm in split_tfms(raw):\n add_evidence(evidence, tfm, file_path, root)\n\n for raw in TARGET_FRAMEWORKS_RE.findall(text):\n for tfm in split_tfms(raw):\n add_evidence(evidence, tfm, file_path, root)\n\n for raw in TARGET_FRAMEWORK_VERSION_RE.findall(text):\n add_evidence(evidence, raw, file_path, root)\n\n return evidence\n\n\ndef collect_file_based_tfms(root: Path) -> dict[str, set[str]]:\n evidence: dict[str, set[str]] = {}\n for file_path in sorted(root.glob(\"**/*.cs\")):\n text = read_text(file_path)\n if text is None:\n continue\n for match in FILE_BASED_TFM_RE.findall(text):\n add_evidence(evidence, match, file_path, root)\n return evidence\n\n\ndef collect_workflow_dotnet_versions(root: Path) -> dict[str, set[str]]:\n evidence: dict[str, set[str]] = {}\n workflows_dir = root / \".github\" / \"workflows\"\n if not workflows_dir.is_dir():\n return evidence\n\n files = list(workflows_dir.glob(\"*.yml\")) + list(workflows_dir.glob(\"*.yaml\"))\n for file_path in sorted(files):\n text = read_text(file_path)\n if text is None:\n continue\n for match in DOTNET_VERSION_RE.findall(text):\n add_evidence(evidence, match, file_path, root)\n return evidence\n\n\ndef read_global_json_sdk(root: Path) -> tuple[str | None, str | None]:\n file_path = root / \"global.json\"\n if not file_path.is_file():\n return None, None\n\n text = read_text(file_path)\n if text is None:\n return None, rel_path(file_path, root)\n\n try:\n data = json.loads(text)\n except json.JSONDecodeError:\n return None, rel_path(file_path, root)\n\n sdk = data.get(\"sdk\")\n if isinstance(sdk, dict):\n version = sdk.get(\"version\")\n if isinstance(version, str) and version.strip():\n return version.strip(), rel_path(file_path, root)\n\n return None, rel_path(file_path, root)\n\n\ndef tfm_sort_key(tfm: str) -> tuple[int, int, str]:\n match = NET_TFM_VERSION_RE.match(tfm.lower())\n if match:\n return int(match.group(1)), int(match.group(2)), tfm\n return -1, -1, tfm\n\n\ndef sdk_to_tfm(sdk_version: str) -> str | None:\n match = SDK_VERSION_RE.match(sdk_version.strip())\n if not match:\n return None\n major = match.group(1)\n minor = match.group(2) or \"0\"\n return f\"net{major}.{minor}\"\n\n\ndef infer_current_target(\n project_tfms: dict[str, set[str]],\n file_tfms: dict[str, set[str]],\n global_sdk_version: str | None,\n workflow_versions: dict[str, set[str]],\n) -> tuple[str | None, str]:\n explicit = sorted({*project_tfms.keys(), *file_tfms.keys()}, key=tfm_sort_key)\n explicit = [tfm for tfm in explicit if NET_TFM_VERSION_RE.match(tfm.lower())]\n if explicit:\n return explicit[-1], \"explicit_target_framework\"\n\n if global_sdk_version:\n inferred = sdk_to_tfm(global_sdk_version)\n if inferred:\n return inferred, \"global_json_sdk\"\n\n inferred_from_workflow = sorted(\n {\n tfm\n for version in workflow_versions.keys()\n for tfm in [sdk_to_tfm(version)]\n if tfm is not None\n },\n key=tfm_sort_key,\n )\n if inferred_from_workflow:\n return inferred_from_workflow[-1], \"workflow_dotnet_version\"\n\n return None, \"none\"\n\n\ndef to_serializable_map(values: dict[str, set[str]]) -> dict[str, list[str]]:\n return {key: sorted(paths) for key, paths in sorted(values.items(), key=lambda x: x[0])}\n\n\ndef print_map(title: str, values: dict[str, set[str]]) -> None:\n print(f\"{title}:\")\n if not values:\n print(\" (none)\")\n return\n for item in sorted(values.keys(), key=tfm_sort_key):\n print(f\" - {item}\")\n for path in sorted(values[item]):\n print(f\" {path}\")\n\n\ndef main() -> int:\n parser = argparse.ArgumentParser(\n description=\"Scan the repository for current .NET target framework and SDK signals.\"\n )\n parser.add_argument(\n \"--root\",\n default=\".\",\n help=\"Repository root to scan (default: current directory).\",\n )\n parser.add_argument(\n \"--json\",\n action=\"store_true\",\n help=\"Emit JSON output instead of a human-readable report.\",\n )\n args = parser.parse_args()\n\n root = Path(args.root).resolve()\n if not root.is_dir():\n print(f\"ERROR: Not a directory: {root}\", file=sys.stderr)\n return 2\n\n project_tfms = collect_project_tfms(root)\n file_based_tfms = collect_file_based_tfms(root)\n workflow_versions = collect_workflow_dotnet_versions(root)\n global_sdk_version, global_json_path = read_global_json_sdk(root)\n inferred_target, inference_source = infer_current_target(\n project_tfms, file_based_tfms, global_sdk_version, workflow_versions\n )\n\n report = {\n \"repo_root\": str(root),\n \"project_target_frameworks\": to_serializable_map(project_tfms),\n \"file_based_target_frameworks\": to_serializable_map(file_based_tfms),\n \"global_json\": {\n \"path\": global_json_path,\n \"sdk_version\": global_sdk_version,\n },\n \"workflow_dotnet_versions\": to_serializable_map(workflow_versions),\n \"inferred_current_target\": inferred_target,\n \"inference_source\": inference_source,\n }\n\n if args.json:\n print(json.dumps(report, indent=2))\n return 0\n\n print(f\"Repository: {root}\")\n print_map(\"Project target frameworks\", project_tfms)\n print_map(\"File-based target frameworks\", file_based_tfms)\n if global_json_path:\n if global_sdk_version:\n print(f\"global.json SDK: {global_sdk_version} ({global_json_path})\")\n else:\n print(f\"global.json SDK: (not found/parseable) ({global_json_path})\")\n else:\n print(\"global.json SDK: (none)\")\n print_map(\"Workflow dotnet-version values\", workflow_versions)\n if inferred_target:\n print(f\"Inferred current target: {inferred_target} ({inference_source})\")\n else:\n print(\"Inferred current target: (none)\")\n\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n","content_type":"text/x-python; charset=utf-8","language":"python","size":8248,"content_sha256":"813cd6a610e3993772110da56d3cbc06bd3e872876002b9d60b2c1d9ff469b9e"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"dotnet-tooling","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":".NET project setup, build systems, performance, CLI apps, and developer tooling. This consolidated skill spans 34 topic areas. Load the appropriate companion file from ","type":"text"},{"text":"references/","type":"text","marks":[{"type":"code_inline"}]},{"text":" based on the routing table below.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Routing Table","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Topic","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Keywords","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Companion File","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Project structure","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"solution, .slnx, CPM, analyzers","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".slnx, Directory.Build.props, CPM, analyzers","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/project-structure.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Scaffold project","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dotnet new, CPM, SourceLink, editorconfig","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dotnet new with CPM, analyzers, editorconfig, SourceLink","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/scaffold-project.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Csproj reading","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PropertyGroup, ItemGroup, CPM, props","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SDK-style .csproj, PropertyGroup, ItemGroup, CPM","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/csproj-reading.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MSBuild authoring","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"targets, props, conditions, Directory.Build","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Targets, props, conditions, Directory.Build patterns","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/msbuild-authoring.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MSBuild tasks","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ITask, ToolTask, inline tasks, UsingTask","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ITask, ToolTask, IIncrementalTask, inline tasks","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/msbuild-tasks.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Build analysis","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MSBuild output, NuGet errors, analyzer warnings","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MSBuild output, NuGet errors, analyzer warnings","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/build-analysis.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Build optimization","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"slow builds, binary logs, parallel, restore","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Slow builds, binary logs, parallel, restore","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/build-optimization.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Artifacts output","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"UseArtifactsOutput, ArtifactsPath, CI/Docker","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"UseArtifactsOutput, ArtifactsPath, CI/Docker impact","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/artifacts-output.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Multi-targeting","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"multiple TFMs, polyfills, conditional compilation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Multiple TFMs, PolySharp, conditional compilation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/multi-targeting.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Performance patterns","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Span, ArrayPool, ref struct, sealed, stackalloc","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Span, ArrayPool, ref struct, sealed, stackalloc","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/performance-patterns.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Profiling","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dotnet-counters, dotnet-trace, flame graphs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dotnet-counters, dotnet-trace, dotnet-dump, flame graphs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/profiling.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native AOT","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PublishAot, ILLink, P/Invoke, size optimization","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PublishAot, ILLink descriptors, P/Invoke, size optimization","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/native-aot.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"AOT architecture","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"source gen, AOT-safe DI, serialization","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Source gen over reflection, AOT-safe DI, factories","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/aot-architecture.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trimming","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"annotations, ILLink, IL2xxx warnings, IsTrimmable","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Annotations, ILLink, IL2xxx warnings, IsTrimmable","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/trimming.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"GC/memory","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"GC modes, LOH/POH, Span/Memory, ArrayPool","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"GC modes, LOH/POH, Gen0/1/2, Span/Memory, ArrayPool","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/gc-memory.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CLI architecture","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"command/handler/service, clig.dev, exit codes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Command/handler/service, clig.dev, exit codes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cli-architecture.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"System.CommandLine","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RootCommand, Option\u003cT>, SetAction, parsing","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"System.CommandLine 2.0, RootCommand, Option\u003cT>","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/system-commandline.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Spectre.Console","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"tables, trees, progress, prompts, live displays","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tables, trees, progress, prompts, live displays","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/spectre-console.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Terminal.Gui","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"views, layout, menus, dialogs, bindings, themes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Terminal.Gui v2, views, layout, menus, dialogs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/terminal-gui.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CLI distribution","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"AOT vs framework-dependent, RID matrix, Homebrew, winget, Scoop, dotnet tool","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Distribution strategy, single-file publish, per-platform packaging","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cli-distribution.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CLI release pipeline","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"GHA build matrix, artifact staging, checksums","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"GHA build matrix, artifact staging, checksums","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cli-release-pipeline.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Documentation strategy","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Starlight, Docusaurus, DocFX decision tree","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Starlight, Docusaurus, DocFX decision tree","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/documentation-strategy.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tool management","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"global, local, manifests, restore, pinning","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Global/local tools, manifests, restore, pinning","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/tool-management.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Version detection","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TFM/SDK from .csproj, global.json","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TFM/SDK from .csproj, global.json, Directory.Build","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/version-detection.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Version upgrade","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LTS-to-LTS, staged, preview, upgrade paths","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LTS-to-LTS, staged through STS, preview paths","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/version-upgrade.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Solution navigation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"entry points, .sln/.slnx, dependency graphs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Entry points, .sln/.slnx, dependency graphs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/solution-navigation.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Project analysis","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"solution layout, build config analysis","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Solution layout, build config, .csproj analysis","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/project-analysis.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Modernize","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"outdated TFMs, deprecated packages, patterns","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Outdated TFMs, deprecated packages, superseded patterns","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/modernize.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add analyzers","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"nullable, trimming, AOT compat, severity config","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Nullable, trimming, AOT compat analyzers, severity","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/add-analyzers.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SDK installation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"install .NET, dotnet-install, workloads, missing SDK","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".NET SDK install script, workloads, env vars, side-by-side","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/dotnet-sdk-install.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ILSpy decompile","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ilspycmd, decompile, assembly, disassemble, IL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ILSpy/ilspycmd decompilation, type listing, IL view","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/ilspy-decompile.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mermaid diagrams","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"architecture, sequence, class, ER, flowcharts","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Architecture, sequence, class, deployment, ER diagrams","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/mermaid-diagrams.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"VS Code debugging","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"launch.json, tasks.json, coreclr, attach, debug","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"VS Code launch/attach configs, tasks, multi-project, hot reload","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/vscode-debug.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"C# LSP","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"language server, csharp-ls, OmniSharp, go to definition","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"C# LSP servers, code navigation, agent usage patterns","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/csharp-lsp.md","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Scope","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Solution structure and project scaffolding","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MSBuild authoring and build optimization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Performance patterns and profiling","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Native AOT, trimming, and GC tuning","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CLI app development (System.CommandLine, Spectre.Console, Terminal.Gui)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Documentation generation (DocFX)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tool management and version detection/upgrade","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Solution navigation and project analysis","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Code modernization and analyzer configuration","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mermaid diagram generation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"VS Code debug configuration (launch.json, tasks.json, coreclr)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"C# LSP servers (csharp-ls, OmniSharp) for agent code navigation","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Out of scope","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Crash dump analysis, hang/deadlock triage, live debugger attach -> [skill:dotnet-debugging]","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Web API patterns -> [skill:dotnet-api]","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test authoring -> [skill:dotnet-testing]","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CI/CD pipelines -> [skill:dotnet-devops]","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"C# language patterns -> [skill:dotnet-csharp]","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"UI framework development -> [skill:dotnet-ui]","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Scripts","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/scan-dotnet-targets.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Scan repository for .NET TFM and SDK version signals","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"dotnet-tooling","author":"@skillopedia","source":{"stars":214,"repo_name":"dotnet-artisan","origin_url":"https://github.com/novotnyllc/dotnet-artisan/blob/HEAD/plugins/dotnet-artisan/skills/dotnet-tooling/SKILL.md","repo_owner":"novotnyllc","body_sha256":"9c7957a37e9bd2ab2061a49295314120c533a5a3d1defb7828e93d2d9009c58d","cluster_key":"1ca1a627def82d7bd01e26cbfe988f3460919f902e067942dc57d296897f8924","clean_bundle":{"format":"clean-skill-bundle-v1","source":"novotnyllc/dotnet-artisan/plugins/dotnet-artisan/skills/dotnet-tooling/SKILL.md","attachments":[{"id":"5a42d470-c982-512b-a6eb-06489411a189","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5a42d470-c982-512b-a6eb-06489411a189/attachment.yaml","path":"agents/openai.yaml","size":289,"sha256":"496b1efda61e8dfb994677e2ed6360935369c5e8905e39fe46e06412aa09db3d","contentType":"application/yaml; charset=utf-8"},{"id":"657a962d-5cb0-5bd7-b924-efad0b307007","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/657a962d-5cb0-5bd7-b924-efad0b307007/attachment.md","path":"references/add-analyzers.md","size":8692,"sha256":"8457b60a86c9f10af94ac9201ecfeab0e049034c40d00dab75f42bb6f94083da","contentType":"text/markdown; charset=utf-8"},{"id":"a967bc07-1c7c-5571-b5d6-0981912a88bb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a967bc07-1c7c-5571-b5d6-0981912a88bb/attachment.md","path":"references/aot-architecture.md","size":11221,"sha256":"917047e057192172547cad05e4f7a949b57c2cfc26dba5c7227d99c7fdc7a3d0","contentType":"text/markdown; charset=utf-8"},{"id":"03f640eb-fa0c-5b92-a090-cc0b00a85ea7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/03f640eb-fa0c-5b92-a090-cc0b00a85ea7/attachment.md","path":"references/artifacts-output.md","size":8728,"sha256":"8909cadb2853ee6dbe9be7f4e8c0f6276854b87f2d4d74b46c77be11645f090c","contentType":"text/markdown; charset=utf-8"},{"id":"93fa6164-90eb-5d43-9517-5eccb43d9cda","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/93fa6164-90eb-5d43-9517-5eccb43d9cda/attachment.md","path":"references/build-analysis.md","size":22023,"sha256":"eb719b62324cd61b1fb96f309ba99bc866235139e896f912e735d7bf0c84258f","contentType":"text/markdown; charset=utf-8"},{"id":"14d3f115-0003-55fc-a760-058a696cc03d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/14d3f115-0003-55fc-a760-058a696cc03d/attachment.md","path":"references/build-optimization.md","size":16696,"sha256":"b3a44f556ccbcdd59e2b647873d376f48f0ffbe7544cd37a98c1e8aac997bc51","contentType":"text/markdown; charset=utf-8"},{"id":"49d1f015-5840-55ba-88a9-2ee6fcd3c3be","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/49d1f015-5840-55ba-88a9-2ee6fcd3c3be/attachment.md","path":"references/cli-architecture.md","size":18456,"sha256":"82749852a1c9d13197381778df444e32da9a3b37c0a7cf03f6235df1e94b526b","contentType":"text/markdown; charset=utf-8"},{"id":"8a87bd58-790c-5545-8454-43484b8a1517","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8a87bd58-790c-5545-8454-43484b8a1517/attachment.md","path":"references/cli-distribution.md","size":16237,"sha256":"680dee0deb0e33e638e7659653f00a8221083c3758f51e743d4cd63598670137","contentType":"text/markdown; charset=utf-8"},{"id":"eeb28918-80ea-5f95-bfa0-78e13eee37dd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/eeb28918-80ea-5f95-bfa0-78e13eee37dd/attachment.md","path":"references/cli-release-pipeline.md","size":16597,"sha256":"d6492c582ae81ab40abb9eed9b8ed2a7682fd1594da285b0f69f5fd89fda3f58","contentType":"text/markdown; charset=utf-8"},{"id":"80783bd5-78ee-5a7e-94ef-b38741f07359","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/80783bd5-78ee-5a7e-94ef-b38741f07359/attachment.md","path":"references/csharp-lsp.md","size":10486,"sha256":"6d20623f952abf89a68194125c8abeacc44c102b1738cabc4f957c30c384e337","contentType":"text/markdown; charset=utf-8"},{"id":"aaa24a0e-67fb-5442-b6e5-529bcdbb642d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/aaa24a0e-67fb-5442-b6e5-529bcdbb642d/attachment.md","path":"references/csproj-reading.md","size":19659,"sha256":"090ea7789365a64f738220a7e1b34a28df0b7134bef7361f2fa51ebf6f295ce9","contentType":"text/markdown; charset=utf-8"},{"id":"10627eff-2b18-5352-b73e-14a1324a293f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/10627eff-2b18-5352-b73e-14a1324a293f/attachment.md","path":"references/documentation-strategy.md","size":17981,"sha256":"402b37716e8df903f6c4a62c4433f10f3e3f8e8b0c98133d85e85640a885bfc1","contentType":"text/markdown; charset=utf-8"},{"id":"5f8fea63-8190-50bb-ba5c-0569ebe4e855","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5f8fea63-8190-50bb-ba5c-0569ebe4e855/attachment.md","path":"references/dotnet-sdk-install.md","size":8241,"sha256":"4e5c9a53412bf47d4dbd3ef3aee56ed81c71ff4a4244ce3ec2ad9e0013ad2e65","contentType":"text/markdown; charset=utf-8"},{"id":"49fcf235-393f-5b21-877b-ab315a5f9827","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/49fcf235-393f-5b21-877b-ab315a5f9827/attachment.md","path":"references/gc-memory.md","size":19587,"sha256":"ebefa08954c5bdbc7c6c1d91ae839cdadcdcfc9bd6a2643d2667ca3e6cfcb650","contentType":"text/markdown; charset=utf-8"},{"id":"b982ba25-14e7-5236-aa3d-1fc423ed7749","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b982ba25-14e7-5236-aa3d-1fc423ed7749/attachment.md","path":"references/ilspy-decompile.md","size":5031,"sha256":"da1f7a38d4f03a4ffea3a24759086cda53e1f761c05054c42e9642e83544dacb","contentType":"text/markdown; charset=utf-8"},{"id":"2ab8eb58-da42-5bfa-9652-d7dcbfbdf27e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2ab8eb58-da42-5bfa-9652-d7dcbfbdf27e/attachment.md","path":"references/mermaid-diagrams.md","size":24081,"sha256":"28b2f5d6a3b3fa97ac5ec50c463dd844370dc2a0951949deda70f5de096532d4","contentType":"text/markdown; charset=utf-8"},{"id":"2b44ed3b-6b90-5a94-84a2-6d8bea30eaeb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2b44ed3b-6b90-5a94-84a2-6d8bea30eaeb/attachment.md","path":"references/modernize.md","size":11708,"sha256":"e143c11c489f2b9c561b8586a18eb6849be92ddc87898f3ff0a4781f9fb24932","contentType":"text/markdown; charset=utf-8"},{"id":"816bce05-8abf-53ed-8706-37429d4e3fd8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/816bce05-8abf-53ed-8706-37429d4e3fd8/attachment.md","path":"references/msbuild-authoring.md","size":20099,"sha256":"ea327752c5de9e53bbd7f512f8a5e1de1db7ebbd352bccebb52aa9f92c218ef2","contentType":"text/markdown; charset=utf-8"},{"id":"6d31ca75-ea40-5ed1-af5e-bbed6c263c09","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6d31ca75-ea40-5ed1-af5e-bbed6c263c09/attachment.md","path":"references/msbuild-tasks.md","size":25048,"sha256":"ddcba4a3daae722a380a8d2203bc3b2372ffb3a517b1afeb99420143960e9d0f","contentType":"text/markdown; charset=utf-8"},{"id":"5ab34f02-39f3-51ae-9e58-755f9a9529c8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5ab34f02-39f3-51ae-9e58-755f9a9529c8/attachment.md","path":"references/multi-targeting.md","size":20024,"sha256":"a68f52012868c5c60c0b3342b20d1b2670eadb6888fe211ace60e542a6c156b3","contentType":"text/markdown; charset=utf-8"},{"id":"2a3b78d8-8142-535a-a7ad-c9fabe009986","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2a3b78d8-8142-535a-a7ad-c9fabe009986/attachment.md","path":"references/native-aot.md","size":14389,"sha256":"83a49a55a8798d0ff637f852541561a534b9e7ddf048e296126a7ea84bfbdb33","contentType":"text/markdown; charset=utf-8"},{"id":"2645aca2-9c40-5bad-bd1f-1854ede4b1c0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2645aca2-9c40-5bad-bd1f-1854ede4b1c0/attachment.md","path":"references/performance-patterns.md","size":15024,"sha256":"5987a75842dc203c6f51b12a434796d330dd4512269da3ed809fecc2fcea7c2a","contentType":"text/markdown; charset=utf-8"},{"id":"47e94648-03a0-5971-9968-fca69e2a7bc3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/47e94648-03a0-5971-9968-fca69e2a7bc3/attachment.md","path":"references/profiling.md","size":17935,"sha256":"e6de6018ac97ca9e8a99b35572293d31dad61b99475c8b65ecca4dc57a36f47e","contentType":"text/markdown; charset=utf-8"},{"id":"070adcc2-68a6-5841-8eb8-7fca8c75e3ea","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/070adcc2-68a6-5841-8eb8-7fca8c75e3ea/attachment.md","path":"references/project-analysis.md","size":14912,"sha256":"68bf561c9b496d22fddf1a160e96fe055ea743396ac1cb828daa73caf944a1d2","contentType":"text/markdown; charset=utf-8"},{"id":"a4b4c881-320f-5f69-9cd7-d525e458a3db","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a4b4c881-320f-5f69-9cd7-d525e458a3db/attachment.md","path":"references/project-structure.md","size":11726,"sha256":"584aaf9bf6a5c6efc1fc1ec6e65d2ca3a9590f6853d1f4f7fc6837f756c95101","contentType":"text/markdown; charset=utf-8"},{"id":"baf7c198-348e-5846-bf31-d94874a9e91f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/baf7c198-348e-5846-bf31-d94874a9e91f/attachment.md","path":"references/scaffold-project.md","size":9592,"sha256":"d1588d3a9a4b1661b5b65c9750e18d58667997d5c13458ad85c74835845c5030","contentType":"text/markdown; charset=utf-8"},{"id":"ea70a389-a239-5c2a-b02a-3795da0cd84f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ea70a389-a239-5c2a-b02a-3795da0cd84f/attachment.md","path":"references/solution-navigation.md","size":18179,"sha256":"990fa9dfd091bbc75ee5a972c9764c916a66caf2122b88d1ee43d7f6a3aea983","contentType":"text/markdown; charset=utf-8"},{"id":"ec4524e8-2ded-5cb2-9b14-6db5babdc3e2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ec4524e8-2ded-5cb2-9b14-6db5babdc3e2/attachment.md","path":"references/spectre-console.md","size":17880,"sha256":"078f8d7c0ae19115c5e01d229c15739c6e9cce6d3b88db569c8fec7b6313074c","contentType":"text/markdown; charset=utf-8"},{"id":"841582be-14db-5a0f-894c-ab62be3279a9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/841582be-14db-5a0f-894c-ab62be3279a9/attachment.md","path":"references/system-commandline.md","size":21424,"sha256":"06deffa432106f5ba43433dcc75d93f6ff55b6643a91c25bac8661139ba5dd76","contentType":"text/markdown; charset=utf-8"},{"id":"3240394f-f8da-5b85-abcd-37b93d3bed9d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3240394f-f8da-5b85-abcd-37b93d3bed9d/attachment.md","path":"references/terminal-gui.md","size":12954,"sha256":"ecf2e49103859ce8172791ae0702797207058bfa49f54ac2f314f024b40e3a07","contentType":"text/markdown; charset=utf-8"},{"id":"04862ba1-f508-556a-bd72-51b3038af925","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/04862ba1-f508-556a-bd72-51b3038af925/attachment.md","path":"references/tool-management.md","size":9398,"sha256":"5451ba4fa26f292d68a458be1a7e04e0632e0e7f591e3ecacf55e8ee2a1e5bd0","contentType":"text/markdown; charset=utf-8"},{"id":"a8ac0172-4afc-5cb6-91d5-21350a46abfb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a8ac0172-4afc-5cb6-91d5-21350a46abfb/attachment.md","path":"references/trimming.md","size":12309,"sha256":"ca1ff97de64e8295e3a152902c826a2275e88b3ed903f8e971eb98fb087dc809","contentType":"text/markdown; charset=utf-8"},{"id":"31b58bf2-34af-51f9-b5cf-50325131fb6c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/31b58bf2-34af-51f9-b5cf-50325131fb6c/attachment.md","path":"references/version-detection.md","size":11900,"sha256":"9655f72d80cfa90c467659213d602fe0800bdc71cf3472e0b61728318bc63144","contentType":"text/markdown; charset=utf-8"},{"id":"85d06186-5a10-5204-91ea-d1fc002adf20","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/85d06186-5a10-5204-91ea-d1fc002adf20/attachment.md","path":"references/version-upgrade.md","size":18750,"sha256":"afc72f8af7afc067c11594e57440f4f12448bf59c4f6826855816ca8abbb4d2e","contentType":"text/markdown; charset=utf-8"},{"id":"5baaba94-6edb-59f5-adc9-215bf9049a01","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5baaba94-6edb-59f5-adc9-215bf9049a01/attachment.md","path":"references/vscode-debug.md","size":13350,"sha256":"276d8d8797bcddee7aab8581755cede4eb2e2eb57ccf8ac90d084364ad6f43f7","contentType":"text/markdown; charset=utf-8"},{"id":"6b503bdb-de04-5b3f-91dc-2f9b4deb35b1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6b503bdb-de04-5b3f-91dc-2f9b4deb35b1/attachment.py","path":"scripts/scan-dotnet-targets.py","size":8248,"sha256":"813cd6a610e3993772110da56d3cbc06bd3e872876002b9d60b2c1d9ff469b9e","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"ddbe027182c1358517c62a19cc490b7ac8e0df84d0934b02f353d04d348d28ed","attachment_count":36,"text_attachments":36,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"plugins/dotnet-artisan/skills/dotnet-tooling/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"security","category_label":"Security"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"security","import_tag":"clean-skills-v1","description":"Manages .NET SDK installation (dotnet-install, workloads), project setup (.slnx, Directory.Build.props, CPM), MSBuild authoring, build optimization, performance (Span, ArrayPool, stackalloc), profiling (dotnet-counters, dotnet-trace), Native AOT/trimming, GC tuning, CLI apps (System.CommandLine, Spectre.Console, Terminal.Gui), ILSpy decompilation, VS Code debug config (launch.json, coreclr, remote), C# LSP (csharp-ls, OmniSharp), and version detection/upgrade. Spans 34 topic areas. Do not use for UI implementation or API security design.","user-invocable":false}},"renderedAt":1782979996540}

dotnet-tooling Overview .NET project setup, build systems, performance, CLI apps, and developer tooling. This consolidated skill spans 34 topic areas. Load the appropriate companion file from based on the routing table below. Routing Table | Topic | Keywords | Description | Companion File | |-------|----------|-------------|----------------| | Project structure | solution, .slnx, CPM, analyzers | .slnx, Directory.Build.props, CPM, analyzers | references/project-structure.md | | Scaffold project | dotnet new, CPM, SourceLink, editorconfig | dotnet new with CPM, analyzers, editorconfig, SourceL…