< Summary

Information
Class: NexusLabs.Needlr.Generators.DiagnosticsGenerator
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/DiagnosticsGenerator.cs
Line coverage
94%
Covered lines: 678
Uncovered lines: 36
Coverable lines: 714
Total lines: 1205
Line coverage: 94.9%
Branch coverage
84%
Covered branches: 285
Total branches: 336
Branch coverage: 84.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GenerateDiagnosticsSource(...)100%11100%
GenerateDependencyGraphMarkdown(...)85.71%14114095.98%
GenerateLifetimeSummaryMarkdown(...)80%202090.16%
GenerateRegistrationIndexMarkdown(...)96.15%525298.34%
GenerateAnalyzerStatusMarkdown(...)100%11100%
FilterTypes(...)100%88100%
FilterPluginTypes(...)100%88100%
FilterDecorators(...)28.57%561440%
FilterFactories(...)50%13857.14%
CalculateMaxDependencyDepth(...)90%1010100%
GetDepth(...)100%1010100%
CalculateHubServices(...)100%1616100%
GenerateOptionsSummaryMarkdown(...)91.17%343497.61%
GetValidatorDescription(...)62.5%10866.66%
FilterOptions(...)50%13857.14%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/DiagnosticsGenerator.cs

#LineLine coverage
 1// Copyright (c) NexusLabs. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Linq;
 7using System.Text;
 8
 9using NexusLabs.Needlr.Generators.Models;
 10
 11namespace NexusLabs.Needlr.Generators;
 12
 13/// <summary>
 14/// Generates diagnostic markdown output files (DependencyGraph.md, LifetimeSummary.md, RegistrationIndex.md, AnalyzerSt
 15/// </summary>
 16internal static class DiagnosticsGenerator
 17{
 18    internal static string GenerateDiagnosticsSource(DiscoveryResult discoveryResult, string assemblyName, string? proje
 19    {
 9520        var builder = new StringBuilder();
 9521        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 9522        var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
 23
 9524        builder.AppendLine("// <auto-generated/>");
 9525        builder.AppendLine("// Needlr Diagnostic Output Metadata");
 9526        builder.AppendLine($"// Generated: {timestamp} UTC");
 9527        builder.AppendLine("// This file contains diagnostic markdown content for extraction by MSBuild.");
 9528        builder.AppendLine();
 9529        builder.AppendLine("#nullable enable");
 9530        builder.AppendLine();
 9531        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 9532        builder.AppendLine();
 9533        builder.AppendLine("/// <summary>");
 9534        builder.AppendLine("/// Contains diagnostic output markdown content generated at compile time.");
 9535        builder.AppendLine("/// Use NeedlrDiagnostics MSBuild property to enable file generation.");
 9536        builder.AppendLine("/// </summary>");
 9537        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 9538        builder.AppendLine("internal static class NeedlrDiagnostics");
 9539        builder.AppendLine("{");
 40
 41        // Output path for MSBuild target
 9542        builder.AppendLine($"    public const string OutputPath = @\"{GeneratorHelpers.EscapeStringLiteral(options.Outpu
 9543        builder.AppendLine();
 44
 45        // Generate all three diagnostic outputs when enabled
 9546        var dependencyGraphContent = GenerateDependencyGraphMarkdown(discoveryResult, assemblyName, timestamp, options.T
 9547        builder.AppendLine("    /// <summary>DependencyGraph.md content</summary>");
 9548        builder.AppendLine($"    public const string DependencyGraph = @\"{GeneratorHelpers.EscapeVerbatimStringLiteral(
 9549        builder.AppendLine();
 50
 9551        var lifetimeSummaryContent = GenerateLifetimeSummaryMarkdown(discoveryResult, assemblyName, timestamp, options.T
 9552        builder.AppendLine("    /// <summary>LifetimeSummary.md content</summary>");
 9553        builder.AppendLine($"    public const string LifetimeSummary = @\"{GeneratorHelpers.EscapeVerbatimStringLiteral(
 9554        builder.AppendLine();
 55
 9556        var registrationIndexContent = GenerateRegistrationIndexMarkdown(discoveryResult, assemblyName, projectDirectory
 9557        builder.AppendLine("    /// <summary>RegistrationIndex.md content</summary>");
 9558        builder.AppendLine($"    public const string RegistrationIndex = @\"{GeneratorHelpers.EscapeVerbatimStringLitera
 9559        builder.AppendLine();
 60
 9561        var analyzerStatusContent = GenerateAnalyzerStatusMarkdown(timestamp);
 9562        builder.AppendLine("    /// <summary>AnalyzerStatus.md content</summary>");
 9563        builder.AppendLine($"    public const string AnalyzerStatus = @\"{GeneratorHelpers.EscapeVerbatimStringLiteral(a
 9564        builder.AppendLine();
 65
 9566        var optionsSummaryContent = GenerateOptionsSummaryMarkdown(discoveryResult, assemblyName, timestamp, options.Typ
 9567        builder.AppendLine("    /// <summary>OptionsSummary.md content</summary>");
 9568        builder.AppendLine($"    public const string OptionsSummary = @\"{GeneratorHelpers.EscapeVerbatimStringLiteral(o
 69
 9570        builder.AppendLine("}");
 71
 9572        return builder.ToString();
 73    }
 74
 75    internal static string GenerateDependencyGraphMarkdown(DiscoveryResult discovery, string assemblyName, string timest
 76    {
 9577        var sb = new StringBuilder();
 9578        var types = FilterTypes(discovery.InjectableTypes, typeFilter);
 79
 9580        sb.AppendLine("# Needlr Dependency Graph");
 9581        sb.AppendLine();
 9582        sb.AppendLine($"Generated: {timestamp} UTC");
 9583        sb.AppendLine($"Assembly: {assemblyName}");
 9584        sb.AppendLine();
 85
 86        // Show referenced TypeRegistry assemblies with their types
 9587        if (referencedAssemblyTypes.Count > 0)
 88        {
 1389            sb.AppendLine("## Referenced Plugin Assemblies");
 1390            sb.AppendLine();
 1391            sb.AppendLine("Types from referenced assemblies with `[GenerateTypeRegistry]`:");
 1392            sb.AppendLine();
 93
 6894            foreach (var kvp in referencedAssemblyTypes.OrderBy(kv => kv.Key))
 95            {
 1496                var refAsm = kvp.Key;
 1497                var refTypes = kvp.Value;
 98
 1499                sb.AppendLine($"### {refAsm}");
 14100                sb.AppendLine();
 14101                sb.AppendLine("```mermaid");
 14102                sb.AppendLine("graph TD");
 103
 104                // Group by lifetime
 32105                var refSingletons = refTypes.Where(t => t.Lifetime == GeneratorLifetime.Singleton).ToList();
 32106                var refScopeds = refTypes.Where(t => t.Lifetime == GeneratorLifetime.Scoped).ToList();
 32107                var refTransients = refTypes.Where(t => t.Lifetime == GeneratorLifetime.Transient).ToList();
 108
 14109                if (refSingletons.Any())
 110                {
 14111                    sb.AppendLine($"    subgraph Singleton[\"{refAsm} - Singleton\"]");
 60112                    foreach (var type in refSingletons)
 113                    {
 16114                        var shape = type.IsDecorator ? "[[" : (type.HasFactory ? "{{" : "[");
 16115                        var endShape = type.IsDecorator ? "]]" : (type.HasFactory ? "}}" : "]");
 16116                        sb.AppendLine($"        {GeneratorHelpers.GetMermaidNodeId(type.FullName)}{shape}\"{type.ShortNa
 117                    }
 14118                    sb.AppendLine("    end");
 119                }
 14120                if (refScopeds.Any())
 121                {
 2122                    sb.AppendLine($"    subgraph Scoped[\"{refAsm} - Scoped\"]");
 8123                    foreach (var type in refScopeds)
 124                    {
 2125                        var shape = type.IsDecorator ? "[[" : (type.HasFactory ? "{{" : "[");
 2126                        var endShape = type.IsDecorator ? "]]" : (type.HasFactory ? "}}" : "]");
 2127                        sb.AppendLine($"        {GeneratorHelpers.GetMermaidNodeId(type.FullName)}{shape}\"{type.ShortNa
 128                    }
 2129                    sb.AppendLine("    end");
 130                }
 14131                if (refTransients.Any())
 132                {
 0133                    sb.AppendLine($"    subgraph Transient[\"{refAsm} - Transient\"]");
 0134                    foreach (var type in refTransients)
 135                    {
 0136                        var shape = type.IsDecorator ? "[[" : (type.HasFactory ? "{{" : "[");
 0137                        var endShape = type.IsDecorator ? "]]" : (type.HasFactory ? "}}" : "]");
 0138                        sb.AppendLine($"        {GeneratorHelpers.GetMermaidNodeId(type.FullName)}{shape}\"{type.ShortNa
 139                    }
 0140                    sb.AppendLine("    end");
 141                }
 142
 143                // Show dependency edges within this assembly
 32144                var refTypeNames = new HashSet<string>(refTypes.Select(t => t.ShortName), StringComparer.Ordinal);
 64145                foreach (var type in refTypes)
 146                {
 40147                    foreach (var dep in type.Dependencies)
 148                    {
 2149                        var depShort = GeneratorHelpers.GetShortTypeName(dep);
 2150                        var matchingType = refTypes.FirstOrDefault(t =>
 4151                            t.ShortName == depShort ||
 6152                            t.Interfaces.Any(i => GeneratorHelpers.GetShortTypeName(i) == depShort));
 2153                        if (matchingType.FullName != null)
 154                        {
 2155                            sb.AppendLine($"    {GeneratorHelpers.GetMermaidNodeId(type.FullName)} --> {GeneratorHelpers
 156                        }
 157                    }
 158                }
 159
 160                // Show factory→product edges (dotted arrows)
 32161                var factorySources = refTypes.Where(t => t.HasFactory).ToList();
 34162                foreach (var source in factorySources)
 163                {
 3164                    var factoryName = source.ShortName + "Factory";
 6165                    var matchingFactory = refTypes.FirstOrDefault(t => t.ShortName == factoryName);
 3166                    if (matchingFactory.FullName != null)
 167                    {
 0168                        sb.AppendLine($"    {GeneratorHelpers.GetMermaidNodeId(matchingFactory.FullName)} -.->|produces|
 169                    }
 170                }
 171
 14172                sb.AppendLine("```");
 14173                sb.AppendLine();
 174
 175                // Show summary table for this assembly
 14176                sb.AppendLine($"| Service | Lifetime | Interfaces |");
 14177                sb.AppendLine($"|---------|----------|------------|");
 82178                foreach (var type in refTypes.OrderBy(t => t.ShortName))
 179                {
 18180                    var interfaces = type.Interfaces.Any() ? string.Join(", ", type.Interfaces.Select(GeneratorHelpers.G
 18181                    sb.AppendLine($"| {type.ShortName} | {type.Lifetime} | {interfaces} |");
 182                }
 14183                sb.AppendLine();
 184            }
 185        }
 186
 45552187        var singletons = types.Where(t => t.Lifetime == GeneratorLifetime.Singleton).ToList();
 45552188        var scopeds = types.Where(t => t.Lifetime == GeneratorLifetime.Scoped).ToList();
 45552189        var transients = types.Where(t => t.Lifetime == GeneratorLifetime.Transient).ToList();
 190
 95191        sb.AppendLine("## Service Dependencies");
 95192        sb.AppendLine();
 95193        sb.AppendLine("```mermaid");
 95194        sb.AppendLine("graph TD");
 195
 196        // Emit subgraphs by lifetime
 95197        if (singletons.Any())
 198        {
 95199            sb.AppendLine("    subgraph Singleton");
 91100200            foreach (var type in singletons)
 45455201                sb.AppendLine($"        {GeneratorHelpers.GetMermaidNodeId(type.TypeName)}[\"{GeneratorHelpers.GetShortT
 95202            sb.AppendLine("    end");
 203        }
 95204        if (scopeds.Any())
 205        {
 2206            sb.AppendLine("    subgraph Scoped");
 8207            foreach (var type in scopeds)
 2208                sb.AppendLine($"        {GeneratorHelpers.GetMermaidNodeId(type.TypeName)}[\"{GeneratorHelpers.GetShortT
 2209            sb.AppendLine("    end");
 210        }
 95211        if (transients.Any())
 212        {
 0213            sb.AppendLine("    subgraph Transient");
 0214            foreach (var type in transients)
 0215                sb.AppendLine($"        {GeneratorHelpers.GetMermaidNodeId(type.TypeName)}[\"{GeneratorHelpers.GetShortT
 0216            sb.AppendLine("    end");
 217        }
 218
 219        // Emit edges for dependencies
 45552220        var typeNames = new HashSet<string>(types.Select(t => GeneratorHelpers.GetShortTypeName(t.TypeName)), StringComp
 91104221        foreach (var type in types)
 222        {
 112402223            foreach (var dep in type.ConstructorParameterTypes)
 224            {
 10744225                var depShort = GeneratorHelpers.GetShortTypeName(dep);
 226                // Find if we have a type implementing this interface
 10744227                var matchingType = types.FirstOrDefault(t =>
 5333704228                    GeneratorHelpers.GetShortTypeName(t.TypeName) == depShort ||
 5346365229                    t.InterfaceNames.Any(i => GeneratorHelpers.GetShortTypeName(i) == depShort));
 230
 10744231                if (matchingType.TypeName != null)
 3544232                    sb.AppendLine($"    {GeneratorHelpers.GetMermaidNodeId(type.TypeName)} --> {GeneratorHelpers.GetMerm
 233            }
 234        }
 235
 95236        sb.AppendLine("```");
 95237        sb.AppendLine();
 238
 239        // Decorator chains section (aggregates host and plugin decorators)
 95240        var pluginDecorators = referencedAssemblyTypes
 34241            .SelectMany(kv => kv.Value.Where(t => t.IsDecorator).Select(t => (Assembly: kv.Key, Type: t)))
 95242            .ToList();
 243
 95244        if (discovery.Decorators.Any() || pluginDecorators.Any())
 245        {
 7246            sb.AppendLine("## Decorator Chains");
 7247            sb.AppendLine();
 7248            sb.AppendLine("```mermaid");
 7249            sb.AppendLine("graph LR");
 250
 251            // Host decorators - group by service type
 7252            var decoratorsByService = discovery.Decorators
 11253                .GroupBy(d => d.ServiceTypeName)
 14254                .OrderBy(g => g.Key);
 255
 28256            foreach (var serviceGroup in decoratorsByService)
 257            {
 7258                var serviceShortName = GeneratorHelpers.GetShortTypeName(serviceGroup.Key);
 15259                var orderedDecorators = serviceGroup.OrderByDescending(d => d.Order).ToList();
 260
 261                // Find the underlying implementation for this service
 7262                var implementation = types.FirstOrDefault(t =>
 1226263                    t.InterfaceNames.Any(i => GeneratorHelpers.GetShortTypeName(i) == serviceShortName) &&
 1235264                    !discovery.Decorators.Any(d => GeneratorHelpers.GetShortTypeName(d.DecoratorTypeName) == GeneratorHe
 265
 266                // Build the chain: highest order decorator -> ... -> lowest order decorator -> implementation
 36267                for (int i = 0; i < orderedDecorators.Count; i++)
 268                {
 11269                    var decorator = orderedDecorators[i];
 11270                    var decoratorId = GeneratorHelpers.GetMermaidNodeId(decorator.DecoratorTypeName);
 11271                    var decoratorName = GeneratorHelpers.GetShortTypeName(decorator.DecoratorTypeName);
 272
 273                    // Add node definition
 11274                    sb.AppendLine($"    {decoratorId}[[\"{decoratorName}\"]]");
 275
 276                    // Add edge to next decorator or implementation
 11277                    if (i < orderedDecorators.Count - 1)
 278                    {
 4279                        var nextDecorator = orderedDecorators[i + 1];
 4280                        sb.AppendLine($"    {decoratorId} --> {GeneratorHelpers.GetMermaidNodeId(nextDecorator.Decorator
 281                    }
 7282                    else if (implementation.TypeName != null)
 283                    {
 7284                        var implId = GeneratorHelpers.GetMermaidNodeId(implementation.TypeName);
 7285                        var implName = GeneratorHelpers.GetShortTypeName(implementation.TypeName);
 7286                        sb.AppendLine($"    {implId}[\"{implName}\"]");
 7287                        sb.AppendLine($"    {decoratorId} --> {implId}");
 288                    }
 289                }
 290            }
 291
 292            // Plugin decorators (we don't have chain order info, just show the decorator types)
 20293            foreach (var (assembly, type) in pluginDecorators.OrderBy(x => x.Type.ShortName))
 294            {
 2295                var decoratorId = GeneratorHelpers.GetMermaidNodeId(type.FullName);
 2296                var decoratorName = type.ShortName;
 2297                sb.AppendLine($"    {decoratorId}[[\"{decoratorName}\"]]");
 298            }
 299
 7300            sb.AppendLine("```");
 7301            sb.AppendLine();
 302        }
 303
 304        // Intercepted services section (aggregates host and plugin interceptors)
 95305        var interceptedServices = discovery.InterceptedServices.ToList();
 95306        var pluginInterceptors = referencedAssemblyTypes
 34307            .SelectMany(kv => kv.Value.Where(t => t.HasInterceptorProxy).Select(t => (Assembly: kv.Key, Type: t)))
 95308            .ToList();
 309
 95310        if (interceptedServices.Any() || pluginInterceptors.Any())
 311        {
 3312            sb.AppendLine("## Intercepted Services");
 3313            sb.AppendLine();
 3314            sb.AppendLine("```mermaid");
 3315            sb.AppendLine("graph LR");
 316
 317            // Host intercepted services
 15318            foreach (var service in interceptedServices.OrderBy(s => s.TypeName))
 319            {
 3320                var targetId = GeneratorHelpers.GetMermaidNodeId(service.TypeName);
 3321                var targetName = GeneratorHelpers.GetShortTypeName(service.TypeName);
 3322                var proxyId = targetId + "_Proxy";
 3323                var proxyName = targetName + "_InterceptorProxy";
 324
 325                // Target service
 3326                sb.AppendLine($"    {targetId}[\"{targetName}\"]");
 327                // Proxy with stadium shape
 3328                sb.AppendLine($"    {proxyId}[[\"{proxyName}\"]]");
 329                // Edge showing proxy wraps target
 3330                sb.AppendLine($"    {proxyId} -.->|wraps| {targetId}");
 331
 332                // Show interceptors applied
 12333                foreach (var interceptorType in service.AllInterceptorTypeNames)
 334                {
 3335                    var interceptorId = GeneratorHelpers.GetMermaidNodeId(interceptorType);
 3336                    var interceptorName = GeneratorHelpers.GetShortTypeName(interceptorType);
 3337                    sb.AppendLine($"    {interceptorId}([[\"{interceptorName}\"]])");
 3338                    sb.AppendLine($"    {proxyId} --> {interceptorId}");
 339                }
 340            }
 341
 342            // Plugin intercepted services
 12343            foreach (var (assembly, type) in pluginInterceptors.OrderBy(x => x.Type.ShortName))
 344            {
 2345                var targetId = GeneratorHelpers.GetMermaidNodeId(type.FullName);
 2346                var targetName = type.ShortName;
 2347                var proxyId = targetId + "_Proxy";
 2348                var proxyName = targetName + "_InterceptorProxy";
 349
 2350                sb.AppendLine($"    {targetId}[\"{targetName}\"]");
 2351                sb.AppendLine($"    {proxyId}[[\"{proxyName}\"]]");
 2352                sb.AppendLine($"    {proxyId} -.->|wraps| {targetId}");
 353            }
 354
 3355            sb.AppendLine("```");
 3356            sb.AppendLine();
 357        }
 358
 359        // Keyed services section
 45552360        var keyedTypes = types.Where(t => t.IsKeyed).ToList();
 95361        if (keyedTypes.Any())
 362        {
 2363            sb.AppendLine("## Keyed Services");
 2364            sb.AppendLine();
 2365            sb.AppendLine("```mermaid");
 2366            sb.AppendLine("graph TD");
 367
 368            // Group by service key
 2369            var typesByKey = keyedTypes
 10370                .SelectMany(t => t.ServiceKeys.Select(k => (Key: k, Type: t)))
 5371                .GroupBy(x => x.Key)
 6372                .OrderBy(g => g.Key);
 373
 12374            foreach (var keyGroup in typesByKey)
 375            {
 4376                var safeKey = GeneratorHelpers.SanitizeIdentifier(keyGroup.Key);
 4377                sb.AppendLine($"    subgraph key_{safeKey}[\"{keyGroup.Key}\"]");
 23378                foreach (var item in keyGroup.OrderBy(x => x.Type.TypeName))
 379                {
 5380                    var nodeId = GeneratorHelpers.GetMermaidNodeId(item.Type.TypeName);
 5381                    var nodeName = GeneratorHelpers.GetShortTypeName(item.Type.TypeName);
 5382                    sb.AppendLine($"        {nodeId}[\"{nodeName}\"]");
 383                }
 4384                sb.AppendLine("    end");
 385            }
 386
 2387            sb.AppendLine("```");
 2388            sb.AppendLine();
 389        }
 390
 391        // Plugin assemblies section
 95392        var plugins = FilterPluginTypes(discovery.PluginTypes, typeFilter);
 95393        if (plugins.Any())
 394        {
 95395            sb.AppendLine("## Plugin Assemblies");
 95396            sb.AppendLine();
 95397            sb.AppendLine("```mermaid");
 95398            sb.AppendLine("graph TD");
 399
 400            // Group plugins by assembly
 95401            var pluginsByAssembly = plugins
 352402                .GroupBy(p => p.AssemblyName)
 261403                .OrderBy(g => g.Key);
 404
 522405            foreach (var asmGroup in pluginsByAssembly)
 406            {
 166407                var safeAsm = GeneratorHelpers.SanitizeIdentifier(asmGroup.Key);
 166408                var shortAsm = GeneratorHelpers.GetShortTypeName(asmGroup.Key);
 166409                sb.AppendLine($"    subgraph asm_{safeAsm}[\"{shortAsm}\"]");
 1388410                foreach (var plugin in asmGroup.OrderBy(p => p.TypeName))
 411                {
 352412                    var nodeId = GeneratorHelpers.GetMermaidNodeId(plugin.TypeName);
 352413                    var nodeName = GeneratorHelpers.GetShortTypeName(plugin.TypeName);
 414                    // Use stadium shape for plugins
 352415                    sb.AppendLine($"        {nodeId}([\"{nodeName}\"])");
 416                }
 166417                sb.AppendLine("    end");
 418            }
 419
 95420            sb.AppendLine("```");
 95421            sb.AppendLine();
 422        }
 423
 424        // Factory services section (aggregates host and plugin factories)
 95425        var factories = FilterFactories(discovery.Factories, typeFilter);
 95426        var pluginFactories = referencedAssemblyTypes
 35427            .SelectMany(kv => kv.Value.Where(t => t.HasFactory).Select(t => (Assembly: kv.Key, Type: t)))
 95428            .ToList();
 429
 95430        if (factories.Any() || pluginFactories.Any())
 431        {
 6432            sb.AppendLine("## Factory Services");
 6433            sb.AppendLine();
 6434            sb.AppendLine("```mermaid");
 6435            sb.AppendLine("graph LR");
 436
 437            // Host factories
 30438            foreach (var factory in factories.OrderBy(f => f.TypeName))
 439            {
 6440                var sourceNodeId = GeneratorHelpers.GetMermaidNodeId(factory.TypeName);
 6441                var sourceName = GeneratorHelpers.GetShortTypeName(factory.TypeName);
 6442                var factoryNodeId = sourceNodeId + "Factory";
 6443                var factoryName = sourceName + "Factory";
 444
 445                // Generated factory with regular shape
 6446                sb.AppendLine($"    {factoryNodeId}[\"{factoryName}\"]");
 447                // Source type (product) with hexagon shape
 6448                sb.AppendLine($"    {sourceNodeId}{{{{\"{sourceName}\"}}}}");
 449                // Edge showing factory produces the type (dotted arrow)
 6450                sb.AppendLine($"    {factoryNodeId} -.->|produces| {sourceNodeId}");
 451            }
 452
 453            // Plugin factories
 21454            foreach (var (assembly, type) in pluginFactories.OrderBy(f => f.Type.ShortName))
 455            {
 3456                var sourceNodeId = GeneratorHelpers.GetMermaidNodeId(type.FullName);
 3457                var sourceName = type.ShortName;
 3458                var factoryNodeId = sourceNodeId + "Factory";
 3459                var factoryName = sourceName + "Factory";
 460
 461                // Generated factory with regular shape
 3462                sb.AppendLine($"    {factoryNodeId}[\"{factoryName}\"]");
 463                // Source type (product) with hexagon shape
 3464                sb.AppendLine($"    {sourceNodeId}{{{{\"{sourceName}\"}}}}");
 465                // Edge showing factory produces the type (dotted arrow)
 3466                sb.AppendLine($"    {factoryNodeId} -.->|produces| {sourceNodeId}");
 467            }
 468
 6469            sb.AppendLine("```");
 6470            sb.AppendLine();
 471        }
 472
 473        // Interface mapping section
 45552474        var typesWithInterfaces = types.Where(t => t.InterfaceNames.Length > 0).ToList();
 95475        if (typesWithInterfaces.Any())
 476        {
 80477            sb.AppendLine("## Interface Mapping");
 80478            sb.AppendLine();
 80479            sb.AppendLine("```mermaid");
 80480            sb.AppendLine("graph LR");
 481
 556482            foreach (var type in typesWithInterfaces.OrderBy(t => t.TypeName))
 483            {
 132484                var implId = GeneratorHelpers.GetMermaidNodeId(type.TypeName);
 132485                var implName = GeneratorHelpers.GetShortTypeName(type.TypeName);
 132486                sb.AppendLine($"    {implId}[\"{implName}\"]");
 487
 534488                foreach (var iface in type.InterfaceNames)
 489                {
 135490                    var ifaceId = GeneratorHelpers.GetMermaidNodeId(iface);
 135491                    var ifaceName = GeneratorHelpers.GetShortTypeName(iface);
 492                    // Interface uses rounded box, dotted edge points from interface to impl
 135493                    sb.AppendLine($"    {ifaceId}((\"{ifaceName}\"))");
 135494                    sb.AppendLine($"    {ifaceId} -.-> {implId}");
 495                }
 496            }
 497
 80498            sb.AppendLine("```");
 80499            sb.AppendLine();
 500        }
 501
 502        // Complexity metrics section
 95503        sb.AppendLine("## Complexity Metrics");
 95504        sb.AppendLine();
 95505        sb.AppendLine("| Metric | Value |");
 95506        sb.AppendLine("|--------|-------|");
 507
 95508        sb.AppendLine($"| Total Services | {types.Count} |");
 509
 510        // Calculate max dependency depth using BFS
 95511        var maxDepth = CalculateMaxDependencyDepth(types);
 95512        sb.AppendLine($"| Max Dependency Depth | {maxDepth} |");
 513
 514        // Calculate hub services (services that appear as dependencies in 3+ other services)
 95515        var hubServices = CalculateHubServices(types, 3);
 95516        sb.AppendLine($"| Hub Services (≥3 dependents) | {hubServices.Count} |");
 517
 95518        if (hubServices.Any())
 519        {
 75520            sb.AppendLine();
 451521            sb.AppendLine("**Hub Services:** " + string.Join(", ", hubServices.Select(h => $"{GeneratorHelpers.GetShortT
 522        }
 523
 95524        sb.AppendLine();
 525
 526        // Dependency details table
 95527        sb.AppendLine("## Dependency Details");
 95528        sb.AppendLine();
 95529        sb.AppendLine("| Service | Lifetime | Dependencies |");
 95530        sb.AppendLine("|---------|----------|--------------|");
 531
 136561532        foreach (var type in types.OrderBy(t => t.TypeName))
 533        {
 45457534            var deps = type.ConstructorParameterTypes.Any()
 45457535                ? string.Join(", ", type.ConstructorParameterTypes.Select(GeneratorHelpers.GetShortTypeName))
 45457536                : "-";
 45457537            sb.AppendLine($"| {GeneratorHelpers.GetShortTypeName(type.TypeName)} | {type.Lifetime} | {deps} |");
 538        }
 539
 95540        return sb.ToString();
 541    }
 542
 543    internal static string GenerateLifetimeSummaryMarkdown(DiscoveryResult discovery, string assemblyName, string timest
 544    {
 95545        var sb = new StringBuilder();
 95546        var types = FilterTypes(discovery.InjectableTypes, typeFilter);
 547
 45552548        var singletons = types.Where(t => t.Lifetime == GeneratorLifetime.Singleton).ToList();
 45552549        var scopeds = types.Where(t => t.Lifetime == GeneratorLifetime.Scoped).ToList();
 45552550        var transients = types.Where(t => t.Lifetime == GeneratorLifetime.Transient).ToList();
 95551        var total = types.Count;
 552
 95553        sb.AppendLine("# Needlr Lifetime Summary");
 95554        sb.AppendLine();
 95555        sb.AppendLine($"Generated: {timestamp} UTC");
 95556        sb.AppendLine($"Assembly: {assemblyName}");
 95557        sb.AppendLine();
 558
 559        // Referenced plugin assemblies lifetime breakdown
 95560        if (referencedAssemblyTypes.Count > 0)
 561        {
 13562            sb.AppendLine("## Referenced Plugin Assemblies");
 13563            sb.AppendLine();
 564
 68565            foreach (var kvp in referencedAssemblyTypes.OrderBy(kv => kv.Key))
 566            {
 14567                var refAsm = kvp.Key;
 14568                var refTypes = kvp.Value;
 32569                var refSingletons = refTypes.Count(t => t.Lifetime == GeneratorLifetime.Singleton);
 32570                var refScopeds = refTypes.Count(t => t.Lifetime == GeneratorLifetime.Scoped);
 32571                var refTransients = refTypes.Count(t => t.Lifetime == GeneratorLifetime.Transient);
 14572                var refTotal = refTypes.Count;
 573
 14574                sb.AppendLine($"### {refAsm}");
 14575                sb.AppendLine();
 14576                sb.AppendLine("| Lifetime | Count | % |");
 14577                sb.AppendLine("|----------|-------|---|");
 14578                if (refTotal > 0)
 579                {
 14580                    sb.AppendLine($"| Singleton | {refSingletons} | {GeneratorHelpers.Percentage(refSingletons, refTotal
 14581                    sb.AppendLine($"| Scoped | {refScopeds} | {GeneratorHelpers.Percentage(refScopeds, refTotal)}% |");
 14582                    sb.AppendLine($"| Transient | {refTransients} | {GeneratorHelpers.Percentage(refTransients, refTotal
 14583                    sb.AppendLine($"| **Total** | **{refTotal}** | 100% |");
 584                }
 14585                sb.AppendLine();
 586            }
 587        }
 588
 95589        sb.AppendLine("## Registration Counts");
 95590        sb.AppendLine();
 95591        sb.AppendLine("| Lifetime | Count | % |");
 95592        sb.AppendLine("|----------|-------|---|");
 593
 95594        if (total > 0)
 595        {
 95596            sb.AppendLine($"| Singleton | {singletons.Count} | {GeneratorHelpers.Percentage(singletons.Count, total)}% |
 95597            sb.AppendLine($"| Scoped | {scopeds.Count} | {GeneratorHelpers.Percentage(scopeds.Count, total)}% |");
 95598            sb.AppendLine($"| Transient | {transients.Count} | {GeneratorHelpers.Percentage(transients.Count, total)}% |
 95599            sb.AppendLine($"| **Total** | **{total}** | 100% |");
 600        }
 601        else
 602        {
 0603            sb.AppendLine("| (none) | 0 | - |");
 604        }
 605
 95606        sb.AppendLine();
 607
 608        // List by category
 95609        if (singletons.Any())
 610        {
 95611            sb.AppendLine($"## Singleton ({singletons.Count})");
 95612            sb.AppendLine();
 136555613            foreach (var type in singletons.OrderBy(t => t.TypeName))
 45455614                sb.AppendLine($"- {GeneratorHelpers.GetShortTypeName(type.TypeName)}");
 95615            sb.AppendLine();
 616        }
 617
 95618        if (scopeds.Any())
 619        {
 2620            sb.AppendLine($"## Scoped ({scopeds.Count})");
 2621            sb.AppendLine();
 10622            foreach (var type in scopeds.OrderBy(t => t.TypeName))
 2623                sb.AppendLine($"- {GeneratorHelpers.GetShortTypeName(type.TypeName)}");
 2624            sb.AppendLine();
 625        }
 626
 95627        if (transients.Any())
 628        {
 0629            sb.AppendLine($"## Transient ({transients.Count})");
 0630            sb.AppendLine();
 0631            foreach (var type in transients.OrderBy(t => t.TypeName))
 0632                sb.AppendLine($"- {GeneratorHelpers.GetShortTypeName(type.TypeName)}");
 0633            sb.AppendLine();
 634        }
 635
 95636        return sb.ToString();
 637    }
 638
 639    internal static string GenerateRegistrationIndexMarkdown(DiscoveryResult discovery, string assemblyName, string? pro
 640    {
 95641        var sb = new StringBuilder();
 95642        var types = FilterTypes(discovery.InjectableTypes, typeFilter);
 95643        var plugins = FilterPluginTypes(discovery.PluginTypes, typeFilter);
 95644        var decorators = FilterDecorators(discovery.Decorators, typeFilter);
 645
 95646        sb.AppendLine("# Needlr Registration Index");
 95647        sb.AppendLine();
 95648        sb.AppendLine($"Generated: {timestamp} UTC");
 95649        sb.AppendLine($"Assembly: {assemblyName}");
 95650        sb.AppendLine();
 651
 652        // Referenced plugin assemblies services
 95653        if (referencedAssemblyTypes.Count > 0)
 654        {
 13655            sb.AppendLine("## Referenced Plugin Assemblies");
 13656            sb.AppendLine();
 657
 68658            foreach (var kvp in referencedAssemblyTypes.OrderBy(kv => kv.Key))
 659            {
 14660                var refAsm = kvp.Key;
 14661                var refTypes = kvp.Value;
 662
 14663                sb.AppendLine($"### {refAsm} ({refTypes.Count} services)");
 14664                sb.AppendLine();
 14665                sb.AppendLine("| # | Interface | Implementation | Lifetime |");
 14666                sb.AppendLine("|---|-----------|----------------|----------|");
 667
 14668                var index = 1;
 82669                foreach (var type in refTypes.OrderBy(t => t.ShortName))
 670                {
 18671                    var iface = type.Interfaces.FirstOrDefault() ?? "-";
 18672                    sb.AppendLine($"| {index} | {GeneratorHelpers.GetShortTypeName(iface)} | {type.ShortName} | {type.Li
 18673                    index++;
 674                }
 14675                sb.AppendLine();
 676            }
 677        }
 678
 679        // Services table
 95680        sb.AppendLine($"## Services ({types.Count})");
 95681        sb.AppendLine();
 682
 95683        if (types.Any())
 684        {
 95685            sb.AppendLine("| # | Interface | Implementation | Lifetime | Source |");
 95686            sb.AppendLine("|---|-----------|----------------|----------|--------|");
 687
 95688            var index = 1;
 136561689            foreach (var type in types.OrderBy(t => t.TypeName))
 690            {
 45457691                var iface = type.InterfaceNames.FirstOrDefault() ?? "-";
 45457692                var source = BreadcrumbWriter.GetRelativeSourcePath(type.SourceFilePath, projectDirectory);
 45457693                sb.AppendLine($"| {index} | {GeneratorHelpers.GetShortTypeName(iface)} | {GeneratorHelpers.GetShortTypeN
 45457694                index++;
 695            }
 95696            sb.AppendLine();
 697        }
 698        else
 699        {
 0700            sb.AppendLine("No injectable services discovered.");
 0701            sb.AppendLine();
 702        }
 703
 704        // Decorators section (aggregates host and plugin decorators)
 95705        var pluginDecorators = referencedAssemblyTypes
 34706            .SelectMany(kv => kv.Value.Where(t => t.IsDecorator).Select(t => (Assembly: kv.Key, Type: t)))
 95707            .ToList();
 708
 95709        if (decorators.Any() || pluginDecorators.Any())
 710        {
 7711            var totalCount = decorators.Count + pluginDecorators.Count;
 7712            sb.AppendLine($"## Decorators ({totalCount})");
 7713            sb.AppendLine();
 7714            sb.AppendLine("| Service | Decorator Chain | Assembly |");
 7715            sb.AppendLine("|---------|-----------------|----------|");
 716
 717            // Host decorators
 7718            var decoratorsByTarget = decorators
 11719                .GroupBy(d => d.ServiceTypeName)
 14720                .OrderBy(g => g.Key);
 721
 28722            foreach (var group in decoratorsByTarget)
 723            {
 7724                var chain = string.Join(" → ",
 11725                    group.OrderBy(d => d.Order)
 18726                         .Select(d => GeneratorHelpers.GetShortTypeName(d.DecoratorTypeName)));
 7727                sb.AppendLine($"| {GeneratorHelpers.GetShortTypeName(group.Key)} | {chain} | (host) |");
 728            }
 729
 730            // Plugin decorators
 20731            foreach (var (assembly, type) in pluginDecorators.OrderBy(x => x.Type.ShortName))
 732            {
 733                // For plugin decorators, we show them individually since we don't have chain info
 2734                var serviceName = type.Interfaces.FirstOrDefault() ?? "-";
 2735                sb.AppendLine($"| {GeneratorHelpers.GetShortTypeName(serviceName)} | {type.ShortName} | {assembly} |");
 736            }
 7737            sb.AppendLine();
 738        }
 739
 740        // Interceptors section (aggregates host and plugin interceptors)
 95741        var interceptedServices = discovery.InterceptedServices.ToList();
 95742        var pluginIntercepted = referencedAssemblyTypes
 34743            .SelectMany(kv => kv.Value.Where(t => t.HasInterceptorProxy).Select(t => (Assembly: kv.Key, Type: t)))
 95744            .ToList();
 745
 95746        if (interceptedServices.Any() || pluginIntercepted.Any())
 747        {
 3748            var totalCount = interceptedServices.Count + pluginIntercepted.Count;
 3749            sb.AppendLine($"## Intercepted Services ({totalCount})");
 3750            sb.AppendLine();
 3751            sb.AppendLine("| Service | Interceptors | Proxy | Assembly |");
 3752            sb.AppendLine("|---------|--------------|-------|----------|");
 753
 754            // Host intercepted services
 15755            foreach (var service in interceptedServices.OrderBy(s => s.TypeName))
 756            {
 3757                var serviceName = GeneratorHelpers.GetShortTypeName(service.TypeName);
 3758                var interceptors = string.Join(", ", service.AllInterceptorTypeNames.Select(GeneratorHelpers.GetShortTyp
 3759                var proxyName = serviceName + "_InterceptorProxy";
 3760                sb.AppendLine($"| {serviceName} | {interceptors} | {proxyName} | (host) |");
 761            }
 762
 763            // Plugin intercepted services
 12764            foreach (var (assembly, type) in pluginIntercepted.OrderBy(x => x.Type.ShortName))
 765            {
 2766                var proxyName = type.ShortName + "_InterceptorProxy";
 2767                sb.AppendLine($"| {type.ShortName} | (see plugin) | {proxyName} | {assembly} |");
 768            }
 3769            sb.AppendLine();
 770        }
 771
 772        // Factories section (aggregates host and plugin factories)
 95773        var factories = discovery.Factories.ToList();
 95774        var pluginFactories = referencedAssemblyTypes
 35775            .SelectMany(kv => kv.Value.Where(t => t.HasFactory).Select(t => (Assembly: kv.Key, Type: t)))
 95776            .ToList();
 777
 95778        if (factories.Any() || pluginFactories.Any())
 779        {
 6780            var totalCount = factories.Count + pluginFactories.Count;
 6781            sb.AppendLine($"## Factories ({totalCount})");
 6782            sb.AppendLine();
 6783            sb.AppendLine("| Source Type | Factory Interface | Generated Factory | Assembly |");
 6784            sb.AppendLine("|-------------|-------------------|-------------------|----------|");
 785
 786            // Host factories
 30787            foreach (var factory in factories.OrderBy(f => f.TypeName))
 788            {
 6789                var sourceName = factory.SimpleTypeName;
 6790                var factoryInterface = "I" + sourceName + "Factory";
 6791                var factoryImpl = sourceName + "Factory";
 6792                sb.AppendLine($"| {sourceName} | {factoryInterface} | {factoryImpl} | (host) |");
 793            }
 794
 795            // Plugin factories
 21796            foreach (var (assembly, type) in pluginFactories.OrderBy(x => x.Type.ShortName))
 797            {
 3798                var factoryInterface = "I" + type.ShortName + "Factory";
 3799                var factoryImpl = type.ShortName + "Factory";
 3800                sb.AppendLine($"| {type.ShortName} | {factoryInterface} | {factoryImpl} | {assembly} |");
 801            }
 6802            sb.AppendLine();
 803        }
 804
 805        // Plugins section
 95806        if (plugins.Any())
 807        {
 799808            var orderedPlugins = plugins.OrderBy(p => p.Order).ThenBy(p => p.TypeName);
 809
 95810            sb.AppendLine($"## Plugins ({plugins.Count})");
 95811            sb.AppendLine();
 95812            sb.AppendLine("| Order | Plugin | Interfaces |");
 95813            sb.AppendLine("|-------|--------|------------|");
 814
 894815            foreach (var plugin in orderedPlugins)
 816            {
 352817                var interfaces = string.Join(", ", plugin.InterfaceNames.Select(GeneratorHelpers.GetShortTypeName));
 352818                sb.AppendLine($"| {plugin.Order} | {GeneratorHelpers.GetShortTypeName(plugin.TypeName)} | {interfaces} |
 819            }
 95820            sb.AppendLine();
 821        }
 822
 823        // Keyed Services section
 45552824        var keyedTypes = types.Where(t => t.ServiceKeys.Length > 0).ToList();
 95825        if (keyedTypes.Any())
 826        {
 7827            sb.AppendLine($"## Keyed Services ({keyedTypes.Sum(t => t.ServiceKeys.Length)})");
 2828            sb.AppendLine();
 2829            sb.AppendLine("| Key | Interface | Implementation | Lifetime |");
 2830            sb.AppendLine("|-----|-----------|----------------|----------|");
 831
 19832            foreach (var type in keyedTypes.OrderBy(t => t.TypeName))
 833            {
 20834                foreach (var key in type.ServiceKeys)
 835                {
 5836                    var iface = type.InterfaceNames.FirstOrDefault() ?? "-";
 5837                    sb.AppendLine($"| `\"{key}\"` | {GeneratorHelpers.GetShortTypeName(iface)} | {GeneratorHelpers.GetSh
 838                }
 839            }
 2840            sb.AppendLine();
 841        }
 842
 95843        return sb.ToString();
 844    }
 845
 846    internal static string GenerateAnalyzerStatusMarkdown(string timestamp)
 847    {
 95848        var sb = new StringBuilder();
 849
 95850        sb.AppendLine("# Needlr Analyzer Status");
 95851        sb.AppendLine();
 95852        sb.AppendLine($"Generated: {timestamp} UTC");
 95853        sb.AppendLine();
 95854        sb.AppendLine("## Active Analyzers");
 95855        sb.AppendLine();
 95856        sb.AppendLine("| ID | Name | Status | Default Severity | Description |");
 95857        sb.AppendLine("|:---|:-----|:-------|:-----------------|:------------|");
 95858        sb.AppendLine("| NDLRCOR001 | Reflection in AOT | ⚪ Conditional | Error | Detects reflection APIs in AOT project
 95859        sb.AppendLine("| NDLRCOR002 | Plugin Constructor | ✅ Active | Warning | Plugins should have parameterless constr
 95860        sb.AppendLine("| NDLRCOR003 | DeferToContainer in Generated | ✅ Active | Error | [DeferToContainer] must be on u
 95861        sb.AppendLine("| NDLRCOR004 | Global Namespace Type | ✅ Active | Warning | Types in global namespace may not be 
 95862        sb.AppendLine("| NDLRCOR005 | Lifetime Mismatch | ✅ Active | Warning | Detects captive dependencies |");
 95863        sb.AppendLine("| NDLRCOR006 | Circular Dependency | ✅ Active | Error | Detects circular service dependencies |")
 95864        sb.AppendLine("| NDLRCOR007 | Intercept Type | ✅ Active | Error | Intercept type must implement IMethodIntercept
 95865        sb.AppendLine("| NDLRCOR008 | Intercept Without Interface | ✅ Active | Warning | [Intercept] requires interface-
 95866        sb.AppendLine("| NDLRCOR009 | Lazy Resolution | ✅ Active | Info | Lazy<T> references undiscovered type |");
 95867        sb.AppendLine("| NDLRCOR010 | Collection Resolution | ✅ Active | Info | IEnumerable<T> has no implementations |"
 95868        sb.AppendLine("| NDLRCOR011 | Keyed Service Usage | ✅ Active | Info | Tracks [FromKeyedServices] parameter usage
 95869        sb.AppendLine();
 95870        sb.AppendLine("## Mode");
 95871        sb.AppendLine();
 95872        sb.AppendLine("**Source Generation**: Enabled (GenerateTypeRegistry detected)");
 95873        sb.AppendLine();
 95874        sb.AppendLine("## Configuration");
 95875        sb.AppendLine();
 95876        sb.AppendLine("Analyzer severity can be configured via `.editorconfig`:");
 95877        sb.AppendLine();
 95878        sb.AppendLine("```ini");
 95879        sb.AppendLine("# Example: Suppress Lazy resolution warnings");
 95880        sb.AppendLine("dotnet_diagnostic.NDLRCOR009.severity = none");
 95881        sb.AppendLine();
 95882        sb.AppendLine("# Example: Promote to warning");
 95883        sb.AppendLine("dotnet_diagnostic.NDLRCOR009.severity = warning");
 95884        sb.AppendLine("```");
 95885        sb.AppendLine();
 886
 95887        return sb.ToString();
 888    }
 889
 890    internal static IReadOnlyList<DiscoveredType> FilterTypes(IReadOnlyList<DiscoveredType> types, HashSet<string> filte
 891    {
 285892        if (filter == null || filter.Count == 0)
 255893            return types;
 894
 30895        return types.Where(t =>
 18195896            filter.Contains(t.TypeName) ||                                      // global::TestApp.OrderService
 18195897            filter.Contains(GeneratorHelpers.GetShortTypeName(t.TypeName)) ||                    // OrderService
 18195898            filter.Contains(GeneratorHelpers.StripGlobalPrefix(t.TypeName)))                     // TestApp.OrderService
 30899                    .ToList();
 900    }
 901
 902    internal static IReadOnlyList<DiscoveredPlugin> FilterPluginTypes(IReadOnlyList<DiscoveredPlugin> plugins, HashSet<s
 903    {
 190904        if (filter == null || filter.Count == 0)
 170905            return plugins;
 906
 20907        return plugins.Where(p =>
 110908            filter.Contains(p.TypeName) ||
 110909            filter.Contains(GeneratorHelpers.GetShortTypeName(p.TypeName)) ||
 110910            filter.Contains(GeneratorHelpers.StripGlobalPrefix(p.TypeName)))
 20911                      .ToList();
 912    }
 913
 914    internal static IReadOnlyList<DiscoveredDecorator> FilterDecorators(IReadOnlyList<DiscoveredDecorator> decorators, H
 915    {
 95916        if (filter == null || filter.Count == 0)
 85917            return decorators;
 918
 10919        return decorators.Where(d =>
 0920            filter.Contains(d.DecoratorTypeName) ||
 0921            filter.Contains(GeneratorHelpers.GetShortTypeName(d.DecoratorTypeName)) ||
 0922            filter.Contains(GeneratorHelpers.StripGlobalPrefix(d.DecoratorTypeName)) ||
 0923            filter.Contains(d.ServiceTypeName) ||
 0924            filter.Contains(GeneratorHelpers.GetShortTypeName(d.ServiceTypeName)) ||
 0925            filter.Contains(GeneratorHelpers.StripGlobalPrefix(d.ServiceTypeName)))
 10926                         .ToList();
 927    }
 928
 929    internal static IReadOnlyList<DiscoveredFactory> FilterFactories(IReadOnlyList<DiscoveredFactory> factories, HashSet
 930    {
 95931        if (filter == null || filter.Count == 0)
 85932            return factories;
 933
 10934        return factories.Where(f =>
 0935            filter.Contains(f.TypeName) ||
 0936            filter.Contains(GeneratorHelpers.GetShortTypeName(f.TypeName)) ||
 0937            filter.Contains(GeneratorHelpers.StripGlobalPrefix(f.TypeName)))
 10938                        .ToList();
 939    }
 940
 941    internal static int CalculateMaxDependencyDepth(IReadOnlyList<DiscoveredType> types)
 942    {
 95943        if (types.Count == 0) return 0;
 944
 945        // Build a lookup from interface/type name to the type that provides it
 95946        var providerLookup = new Dictionary<string, DiscoveredType>(StringComparer.Ordinal);
 91104947        foreach (var type in types)
 948        {
 45457949            providerLookup[GeneratorHelpers.GetShortTypeName(type.TypeName)] = type;
 91184950            foreach (var iface in type.InterfaceNames)
 135951                providerLookup[GeneratorHelpers.GetShortTypeName(iface)] = type;
 952        }
 953
 954        // Calculate depth for each type using memoization
 95955        var depthCache = new Dictionary<string, int>(StringComparer.Ordinal);
 95956        int maxDepth = 0;
 957
 91104958        foreach (var type in types)
 959        {
 45457960            var depth = GetDepth(type, providerLookup, depthCache, new HashSet<string>());
 45703961            if (depth > maxDepth) maxDepth = depth;
 962        }
 963
 95964        return maxDepth;
 965    }
 966
 967    internal static int GetDepth(DiscoveredType type, Dictionary<string, DiscoveredType> providerLookup, Dictionary<stri
 968    {
 49001969        var key = GeneratorHelpers.GetShortTypeName(type.TypeName);
 970
 49001971        if (cache.TryGetValue(key, out var cached))
 3394972            return cached;
 973
 974        // Cycle detection
 45607975        if (!visiting.Add(key))
 150976            return 0;
 977
 45457978        int maxChildDepth = 0;
 112402979        foreach (var dep in type.ConstructorParameterTypes)
 980        {
 10744981            var depShort = GeneratorHelpers.GetShortTypeName(dep);
 10744982            if (providerLookup.TryGetValue(depShort, out var depType))
 983            {
 3544984                var childDepth = GetDepth(depType, providerLookup, cache, visiting);
 3544985                if (childDepth > maxChildDepth)
 3019986                    maxChildDepth = childDepth;
 987            }
 988        }
 989
 45457990        visiting.Remove(key);
 45457991        var result = maxChildDepth + 1;
 45457992        cache[key] = result;
 45457993        return result;
 994    }
 995
 996    internal static List<(string TypeName, int DependentCount)> CalculateHubServices(IReadOnlyList<DiscoveredType> types
 997    {
 998        // Count how many times each type/interface appears as a dependency
 95999        var dependentCounts = new Dictionary<string, int>(StringComparer.Ordinal);
 1000
 911041001        foreach (var type in types)
 1002        {
 1124021003            foreach (var dep in type.ConstructorParameterTypes)
 1004            {
 107441005                var depShort = GeneratorHelpers.GetShortTypeName(dep);
 107441006                if (!dependentCounts.ContainsKey(depShort))
 46621007                    dependentCounts[depShort] = 0;
 107441008                dependentCounts[depShort]++;
 1009            }
 1010        }
 1011
 1012        // Find types that are depended upon by minDependents or more services
 951013        var hubs = new List<(string TypeName, int DependentCount)>();
 911041014        foreach (var type in types)
 1015        {
 454571016            var shortName = GeneratorHelpers.GetShortTypeName(type.TypeName);
 454571017            var count = 0;
 1018
 1019            // Check if this type's name or any of its interfaces is depended upon
 454571020            if (dependentCounts.TryGetValue(shortName, out var c1))
 14251021                count += c1;
 1022
 911841023            foreach (var iface in type.InterfaceNames)
 1024            {
 1351025                if (dependentCounts.TryGetValue(GeneratorHelpers.GetShortTypeName(iface), out var c2))
 121026                    count += c2;
 1027            }
 1028
 454571029            if (count >= minDependents)
 3761030                hubs.Add((type.TypeName, count));
 1031        }
 1032
 4711033        return hubs.OrderByDescending(h => h.DependentCount).ToList();
 1034    }
 1035
 1036    internal static string GenerateOptionsSummaryMarkdown(DiscoveryResult discovery, string assemblyName, string timesta
 1037    {
 951038        var sb = new StringBuilder();
 951039        var options = FilterOptions(discovery.Options, typeFilter);
 1040
 951041        sb.AppendLine("# Needlr Options Summary");
 951042        sb.AppendLine();
 951043        sb.AppendLine($"Generated: {timestamp} UTC");
 951044        sb.AppendLine($"Assembly: {assemblyName}");
 951045        sb.AppendLine();
 1046
 951047        if (options.Count == 0)
 1048        {
 851049            sb.AppendLine("*No options classes discovered. Add `[Options]` attribute to configuration classes.*");
 851050            sb.AppendLine();
 851051            return sb.ToString();
 1052        }
 1053
 101054        sb.AppendLine($"## Overview");
 101055        sb.AppendLine();
 101056        sb.AppendLine($"| Metric | Count |");
 101057        sb.AppendLine($"|:-------|------:|");
 101058        sb.AppendLine($"| Total Options Classes | {options.Count} |");
 231059        sb.AppendLine($"| Named Options | {options.Count(o => o.IsNamed)} |");
 231060        sb.AppendLine($"| With Validation | {options.Count(o => o.ValidateOnStart)} |");
 231061        sb.AppendLine($"| With External Validator | {options.Count(o => o.HasExternalValidator)} |");
 101062        sb.AppendLine();
 1063
 1064        // Options table
 101065        sb.AppendLine("## Options Classes");
 101066        sb.AppendLine();
 101067        sb.AppendLine("| Class | Section | Name | ValidateOnStart | Validator |");
 101068        sb.AppendLine("|:------|:--------|:-----|:---------------:|:----------|");
 1069
 721070        foreach (var opt in options.OrderBy(o => o.SectionName).ThenBy(o => o.TypeName))
 1071        {
 131072            var shortName = GeneratorHelpers.GetShortTypeName(opt.TypeName);
 131073            var namedLabel = opt.IsNamed ? opt.Name : "-";
 131074            var validateIcon = opt.ValidateOnStart ? "✅" : "❌";
 131075            var validatorInfo = GetValidatorDescription(opt);
 1076
 131077            sb.AppendLine($"| `{shortName}` | `{opt.SectionName}` | {namedLabel} | {validateIcon} | {validatorInfo} |");
 1078        }
 1079
 101080        sb.AppendLine();
 1081
 1082        // Detailed breakdown by section
 361083        var sectionGroups = options.GroupBy(o => o.SectionName).OrderBy(g => g.Key);
 1084
 101085        sb.AppendLine("## Configuration Sections");
 101086        sb.AppendLine();
 1087
 461088        foreach (var group in sectionGroups)
 1089        {
 131090            sb.AppendLine($"### `{group.Key}`");
 131091            sb.AppendLine();
 1092
 651093            foreach (var opt in group.OrderBy(o => o.TypeName))
 1094            {
 131095                var shortName = GeneratorHelpers.GetShortTypeName(opt.TypeName);
 131096                sb.AppendLine($"**{shortName}**");
 131097                sb.AppendLine();
 131098                sb.AppendLine($"- Configuration Path: `{group.Key}`");
 1099
 131100                if (opt.IsNamed)
 11101                    sb.AppendLine($"- Named Options: `{opt.Name}`");
 1102
 131103                sb.AppendLine($"- Validate On Start: {(opt.ValidateOnStart ? "Yes" : "No")}");
 1104
 131105                if (opt.HasExternalValidator)
 1106                {
 01107                    sb.AppendLine($"- External Validator: `{GeneratorHelpers.GetShortTypeName(opt.ValidatorTypeName!)}`"
 1108                }
 1109
 131110                if (opt.HasValidatorMethod)
 1111                {
 21112                    var methodType = opt.ValidatorMethod!.Value.IsStatic ? "static" : "instance";
 21113                    sb.AppendLine($"- Validation Method: `{opt.ValidatorMethod.Value.MethodName}()` ({methodType})");
 1114                }
 111115                else if (opt.ValidateMethodOverride != null)
 1116                {
 01117                    sb.AppendLine($"- Validation Method: `{opt.ValidateMethodOverride}()` (specified but not found)");
 1118                }
 1119
 131120                if (opt.SourceFilePath != null)
 1121                {
 1122                    // Use just the filename for brevity in markdown
 131123                    var fileName = System.IO.Path.GetFileName(opt.SourceFilePath);
 131124                    sb.AppendLine($"- Source: `{fileName}`");
 1125                }
 1126
 131127                sb.AppendLine();
 1128            }
 1129        }
 1130
 1131        // Validation warnings
 231132        var missingValidation = options.Where(o => !o.ValidateOnStart && (o.HasValidatorMethod || o.HasExternalValidator
 101133        if (missingValidation.Count > 0)
 1134        {
 11135            sb.AppendLine("## ⚠️ Potential Issues");
 11136            sb.AppendLine();
 11137            sb.AppendLine("The following options have validators configured but `ValidateOnStart` is disabled:");
 11138            sb.AppendLine();
 1139
 41140            foreach (var opt in missingValidation)
 1141            {
 11142                var shortName = GeneratorHelpers.GetShortTypeName(opt.TypeName);
 11143                sb.AppendLine($"- `{shortName}`: Has validator but won't run at startup");
 1144            }
 1145
 11146            sb.AppendLine();
 11147            sb.AppendLine("*Set `ValidateOnStart = true` in the `[Options]` attribute to enable startup validation.*");
 11148            sb.AppendLine();
 1149        }
 1150
 1151        // Usage example
 101152        sb.AppendLine("## Usage");
 101153        sb.AppendLine();
 101154        sb.AppendLine("Options are automatically registered via source generation. Access them via:");
 101155        sb.AppendLine();
 101156        sb.AppendLine("```csharp");
 101157        sb.AppendLine("// Constructor injection");
 101158        sb.AppendLine("public MyService(IOptions<DatabaseOptions> options) { }");
 101159        sb.AppendLine();
 101160        sb.AppendLine("// Named options");
 101161        sb.AppendLine("public MyService(IOptionsSnapshot<ConnectionOptions> options)");
 101162        sb.AppendLine("{");
 101163        sb.AppendLine("    var primary = options.Get(\"Primary\");");
 101164        sb.AppendLine("}");
 101165        sb.AppendLine("```");
 101166        sb.AppendLine();
 1167
 101168        return sb.ToString();
 1169    }
 1170
 1171    private static string GetValidatorDescription(DiscoveredOptions opt)
 1172    {
 131173        if (opt.HasExternalValidator)
 1174        {
 01175            var validatorShort = GeneratorHelpers.GetShortTypeName(opt.ValidatorTypeName!);
 01176            return $"`{validatorShort}`";
 1177        }
 1178
 131179        if (opt.HasValidatorMethod)
 1180        {
 21181            var methodType = opt.ValidatorMethod!.Value.IsStatic ? "static" : "self";
 21182            return $"`{opt.ValidatorMethod.Value.MethodName}()` ({methodType})";
 1183        }
 1184
 111185        if (opt.ValidateMethodOverride != null)
 1186        {
 01187            return $"`{opt.ValidateMethodOverride}()` (not found)";
 1188        }
 1189
 111190        return "-";
 1191    }
 1192
 1193    internal static IReadOnlyList<DiscoveredOptions> FilterOptions(IReadOnlyList<DiscoveredOptions> options, HashSet<str
 1194    {
 951195        if (filter == null || filter.Count == 0)
 851196            return options;
 1197
 101198        return options.Where(o =>
 01199            filter.Contains(o.TypeName) ||
 01200            filter.Contains(GeneratorHelpers.GetShortTypeName(o.TypeName)) ||
 01201            filter.Contains(GeneratorHelpers.StripGlobalPrefix(o.TypeName)))
 101202                      .ToList();
 1203    }
 1204}
 1205

Methods/Properties

GenerateDiagnosticsSource(NexusLabs.Needlr.Generators.Models.DiscoveryResult,System.String,System.String,NexusLabs.Needlr.Generators.DiagnosticOptions,System.Collections.Generic.IReadOnlyList`1<System.String>,System.Collections.Generic.Dictionary`2<System.String,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiagnosticTypeInfo>>)
GenerateDependencyGraphMarkdown(NexusLabs.Needlr.Generators.Models.DiscoveryResult,System.String,System.String,System.Collections.Generic.HashSet`1<System.String>,System.Collections.Generic.IReadOnlyList`1<System.String>,System.Collections.Generic.Dictionary`2<System.String,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiagnosticTypeInfo>>)
GenerateLifetimeSummaryMarkdown(NexusLabs.Needlr.Generators.Models.DiscoveryResult,System.String,System.String,System.Collections.Generic.HashSet`1<System.String>,System.Collections.Generic.Dictionary`2<System.String,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiagnosticTypeInfo>>)
GenerateRegistrationIndexMarkdown(NexusLabs.Needlr.Generators.Models.DiscoveryResult,System.String,System.String,System.String,System.Collections.Generic.HashSet`1<System.String>,System.Collections.Generic.Dictionary`2<System.String,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiagnosticTypeInfo>>)
GenerateAnalyzerStatusMarkdown(System.String)
FilterTypes(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredType>,System.Collections.Generic.HashSet`1<System.String>)
FilterPluginTypes(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredPlugin>,System.Collections.Generic.HashSet`1<System.String>)
FilterDecorators(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredDecorator>,System.Collections.Generic.HashSet`1<System.String>)
FilterFactories(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredFactory>,System.Collections.Generic.HashSet`1<System.String>)
CalculateMaxDependencyDepth(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredType>)
GetDepth(NexusLabs.Needlr.Generators.Models.DiscoveredType,System.Collections.Generic.Dictionary`2<System.String,NexusLabs.Needlr.Generators.Models.DiscoveredType>,System.Collections.Generic.Dictionary`2<System.String,System.Int32>,System.Collections.Generic.HashSet`1<System.String>)
CalculateHubServices(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredType>,System.Int32)
GenerateOptionsSummaryMarkdown(NexusLabs.Needlr.Generators.Models.DiscoveryResult,System.String,System.String,System.Collections.Generic.HashSet`1<System.String>)
GetValidatorDescription(NexusLabs.Needlr.Generators.Models.DiscoveredOptions)
FilterOptions(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredOptions>,System.Collections.Generic.HashSet`1<System.String>)