AspectGenerator Agent Skill
June 20, 2026 ยท View on GitHub
Use this file as the compact agent-oriented guide for using and reviewing AspectGenerator in consuming projects.
AspectGenerator provides AOP-like compile-time method aspects for C# by rewriting visible call sites with C# interceptors. It is not runtime AOP, does not use runtime proxies, and does not weave IL.
Build-tool requirement
AspectGenerator requires projects that use it to be built with the .NET 10 SDK/compiler.
This is a build-toolchain requirement, not necessarily the same thing as the project target framework requirement. The important part is the SDK/compiler used by dotnet build, MSBuild, or Visual Studio. AspectGenerator itself targets netstandard2.0; consuming projects need the .NET 10 compiler because the generator uses the stable Roslyn interceptor API based on opaque InterceptableLocation data.
Do not describe this as simply "requires .NET 10". Say "requires the .NET 10 SDK/compiler to build".
The legacy preview API InterceptsLocation(filePath, line, character) is not supported.
Package reference rule
AspectGenerator runs in the compilation that references the NuGet package.
Every project whose call sites should be intercepted must reference AspectGenerator directly. A shared aspect library may define aspect attributes and hooks, but applications or libraries containing the actual call sites must also reference AspectGenerator directly.
Use this rule when reviewing multi-project solutions:
aspect definitions live in one project
call sites live in another project
=> both projects usually need direct AspectGenerator package references
The package imports the required MSBuild wiring automatically for direct package consumers.
Generated API ownership
AspectGenerator can generate the authoring/runtime API into the current compilation:
AspectAttribute
AspectInstanceLifetime
InterceptInfo
InterceptInfo<T>
InterceptData<T>
InterceptType
InterceptResult
Void
AspectGeneratorOptionsAttribute
AspectDiagnosticSeverity
Common modes:
single project:
AspectGeneratorGenerateApi=true
AspectGeneratorPublicApi=false
aspect library exposing reusable aspects:
AspectGeneratorGenerateApi=true
AspectGeneratorPublicApi=true
consumer using public API from an aspect library:
AspectGeneratorGenerateApi=false
direct AspectGenerator package reference still required if this project has call sites to intercept
AspectGeneratorOptionsAttribute and AspectDiagnosticSeverity are per-project configuration API. They are not the shared runtime contract for aspect libraries.
MSBuild configuration
Relevant MSBuild properties:
<PropertyGroup>
<AspectGeneratorGenerateApi>true</AspectGeneratorGenerateApi>
<AspectGeneratorPublicApi>false</AspectGeneratorPublicApi>
<AspectGeneratorDebuggerStepThrough>false</AspectGeneratorDebuggerStepThrough>
<AspectGeneratorReportFile>$(BaseIntermediateOutputPath)\GeneratedFiles\AspectGenerator\AspectGeneratorBuildReport.md</AspectGeneratorReportFile>
<AspectGeneratorAspectDiagnosticSeverity>Info</AspectGeneratorAspectDiagnosticSeverity>
<AspectGeneratorInterceptorsNamespace>AspectGenerator</AspectGeneratorInterceptorsNamespace>
</PropertyGroup>
AspectGeneratorInterceptorsNamespace controls the namespace used for generated interceptors. The package appends this value to InterceptorsNamespaces automatically. Do not use InterceptorsPreviewNamespaces.
Interceptor source emission is automatic:
normal build -> analyzes and emits interceptor source
DesignTimeBuild -> analyzes for IDE diagnostics/markers but does not emit interceptor source
There is no public GenerateInterceptors option.
Assembly-level configuration
The same settings can be set in source with the generated assembly attribute:
using AspectGenerator;
[assembly: AspectGeneratorOptions(
GenerateApi = true,
PublicApi = false,
DebuggerStepThrough = false,
AspectDiagnosticSeverity = AspectDiagnosticSeverity.Info,
InterceptorsNamespace = "AspectGenerator")]
Assembly-level options override MSBuild properties for that compilation.
Build report
AspectGenerator writes a human-readable build report during normal builds. The report is intended for developers, reviewers, maintainers, and AI coding agents.
Default path:
obj/GeneratedFiles/AspectGenerator/AspectGeneratorBuildReport.md
The report includes:
summary of generator options
selected target methods
intercepted call sites
applied aspect attributes
declared/effective aspect lifetimes where available
generated interceptor method names
generated source file references
source locations where available
The report is not a compiler diagnostic stream and is not printed to the console by default. It is an inspection artifact. Agents should inspect it when asked to understand what AspectGenerator did in a build.
The report is intentionally human-friendly and agent-friendly, not a minimal deterministic baseline format. Absolute file links, line/column locations, generated source references, Markdown tables, and detailed call-site rows are useful and should not be removed merely because they are not golden-file friendly.
Design-time builds do not write the report.
Optional intercepted-call markers
AG0300 diagnostics mark call sites where AspectGenerator applies aspects. They are controlled by AspectGeneratorAspectDiagnosticSeverity:
Off disables optional markers
Hidden reports hidden markers
Info default; reports informational markers
Warning reports warning markers
Error reports error markers
AG0300 is enabled by default with Info severity. It does not indicate a problem. Set AspectGeneratorAspectDiagnosticSeverity to Off to disable these markers. Use AG0300 for temporary IDE/source inspection and the build report for complete aspect application analysis.
Defining aspects
Define an aspect by creating an attribute class decorated with [Aspect]:
using AspectGenerator;
[Aspect(OnBeforeCall = nameof(OnBeforeCall))]
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
sealed class LogAttribute : Attribute
{
public static void OnBeforeCall(InterceptInfo info)
{
Console.WriteLine($"Calling {info.MemberInfo.Name}");
}
}
Hook names are strings. Prefer nameof(...).
Invalid hook names or signatures should be reported by AG010x diagnostics. If a case fails only through generated-code compiler errors, treat it as an AspectGenerator bug.
Hook model
Current supported hook method style is static hook methods.
Common hook signatures:
public static void OnBeforeCall(InterceptInfo info) {}
public static void OnAfterCall(InterceptInfo info) {}
public static void OnCatch(InterceptInfo info) {}
public static void OnFinally(InterceptInfo info) {}
Typed aspect instance parameter is supported for static lifecycle hooks:
public static void OnBeforeCall(LogAttribute aspect, InterceptInfo info) {}
UseInterceptData=true expects hooks to receive ref InterceptData<T> instead of InterceptInfo / InterceptInfo<T>:
public static void OnBeforeCall(ref InterceptData<int> data) {}
OnCall replaces the target method call. It must match the target signature and must be the last aspect in the aspect chain.
Async hooks such as OnBeforeCallAsync, OnAfterCallAsync, OnCatchAsync, and OnFinallyAsync require supported async targets: Task, Task<T>, ValueTask, or ValueTask<T>. async void is unsupported.
Aspect instance lifetime
AspectAttribute.Lifetime controls applied aspect attribute instance construction:
[Aspect(OnBeforeCall = nameof(OnBeforeCall), Lifetime = AspectInstanceLifetime.Static)]
Values:
Auto generator-selected behavior; currently follows the static fast path unless explicit Instance is requested
Static reuse one lazily initialized aspect instance per generated interceptor/aspect application
Instance create a new applied aspect attribute instance for every intercepted call
Static lifetime stores aspect instances in per-interceptor generated static state holders. These holders are initialized lazily on first use of the corresponding interceptor, so unrelated interceptor aspect instances are not constructed.
Static aspect instances are shared. Treat them as immutable configuration objects or implement synchronization for shared mutable state.
Use this guidance:
Static for most aspects and performance-sensitive systems
Static + InterceptInfo.Tag for cheap per-call state
Instance only when typed per-call state stored in the aspect object is worth the allocation
Aspect constructors and property initializers should be cheap and side-effect free. Static lifetime runs them at most once per generated interceptor/aspect application; Instance lifetime runs them for every intercepted call.
TargetFilter
TargetFilter applies an aspect to matching target methods at assembly or type level. It belongs to the applied aspect attribute, not to [Aspect].
DefaultTargetFilter belongs to [Aspect] on the aspect definition. It lets aspect authors prepend default rules to assembly-level or type-level applications of that aspect. V1 uses simple concatenation:
EffectiveTargetFilter = DefaultTargetFilter + TargetFilter
Later matching rules win. A consumer TargetFilter can override a default exclusion unless it repeats that exclusion later.
Aspect attributes that support filters should expose:
public string? TargetFilter { get; set; }
or:
public string[]? TargetFilter { get; set; }
Each string is split into non-empty lines. Lines starting with # are comments. string[] values concatenate all rules.
Rule types:
unprefixed native AspectGenerator target pattern syntax
pattern: native AspectGenerator target pattern syntax
contains: ordinal substring match against canonical method signature
regex: regex match against canonical method signature
-attributes: exclude targets with a matching method or containing type attribute
-rule exclude rule; last matching rule wins inside one filter set
Example:
[assembly: Log(
TargetFilter = """
# Include service save methods and exclude health checks.
public **.*Service.Save*(..., *CancellationToken)
-contains: HealthCheck
""")]
Native condition rules support method metadata conditions. Different keys in one group are AND. Repeated same keys are OR by default. Leading & forces AND. Leading | starts an alternative condition group. Inline condition expressions support & and |, with & higher precedence. Escape literal operators as \& and \|.
Canonical method signature format:
<accessibility>[ <modifier>...] <return-type> <containing-type>.<method-name>[<type-parameters>](<parameter-types>)
Types are fully qualified without C# aliases. Nullable annotations and parameter names are omitted. Extension method receivers are formatted with this.
The attributes: condition checks method attributes and containing type attributes, including nested containing types. It supports short attribute names, short names without the Attribute suffix, and fully qualified names. attribute: is intentionally not supported.
Target filters select target methods. AspectGenerator still rewrites only call sites visible to the current compilation.
Conditional aspects
Aspect attribute classes may use System.Diagnostics.ConditionalAttribute:
[Conditional("DEBUG")]
[Aspect(OnAfterCall = nameof(OnAfterCall))]
sealed class LogAttribute : Attribute
{
public static void OnAfterCall(InterceptInfo info) {}
}
Multiple [Conditional] attributes are OR. Conditional aspect attributes apply consistently to direct method aspects, type-level TargetFilter, and assembly-level TargetFilter.
Project/tree preprocessor symbols such as DEBUG, TRACE, and MSBuild-defined symbols are supported. Local source-file #define / #undef directive state is not guaranteed to be evaluated with full location sensitivity.
Supported and unsupported scenarios
Supported:
static methods
instance methods
extension methods
generic methods
async Task / Task<T> / ValueTask / ValueTask<T>
ref / out / in parameters
target filters
conditional aspects
Unsupported or outside the interception model:
constructors
properties and indexers
operators
local functions
delegates
reflection
calls compiled in external assemblies
async void
unsupported C# invocation constructs
For external assemblies, rebuild the assembly containing the call site with AspectGenerator enabled.
Diagnostics quick reference
AG0001 OnCall aspect must be last when applied directly
AG0002 OnCall aspect must be last when inferred from method metadata
AG0003 invocation cannot be intercepted by the compiler
AG0004 generated interceptor namespace is not listed in InterceptorsNamespaces
AG0101 hook method not found
AG0102 hook method must be static
AG0103 invalid hook parameter type
AG0104 invalid hook return type
AG0105 OnCall signature mismatch
AG0106 UseInterceptData=true requires ref InterceptData<T>
AG0107 async hook requires supported async target
AG0201 invalid TargetFilter regex
AG0202 invalid TargetFilter rule
AG0204 unknown TargetFilter condition key
AG0205 invalid TargetFilter parameter pattern
AG0206 invalid TargetFilter dotted pattern
AG0208 TargetFilter is used on a method-level aspect attribute
AG0209 invalid AspectGenerator diagnostic severity value
AG0300 optional intercepted-call marker
Agent workflow
When asked to analyze a project using AspectGenerator:
1. Verify the project is built with the .NET 10 SDK/compiler.
2. Find every project containing call sites that should be intercepted.
3. Verify each such project references AspectGenerator directly.
4. Check AspectGeneratorGenerateApi / PublicApi ownership mode.
5. Check AspectGeneratorInterceptorsNamespace and InterceptorsNamespaces wiring.
6. Build normally, not only design-time.
7. Inspect AspectGeneratorBuildReport.md.
8. Use generated Interceptors.g.cs only after reading the report.
9. Treat AG0300 as optional marker diagnostics, not as the complete report.
10. Keep README, wiki, and this SKILL.md synchronized when behavior changes.
When reviewing a change:
look for unexpected target methods
look for missing call sites
look for duplicate aspects
look for wrong aspect ordering
look for OnCall not being last
look for Static vs Instance lifetime mistakes
look for unsupported constructors/properties/operators/delegates/reflection
look for projects that use aspect attributes but forgot a direct package reference
Documentation rules for agents
Use current terminology:
InterceptorsNamespaces, not InterceptorsPreviewNamespaces
AspectGeneratorInterceptorsNamespace, not AG_InterceptorsNamespace
InterceptableLocation, not file/line/column InterceptsLocation
call-site rewriting, not runtime proxying
build report, not runtime log
build-toolchain requirement, not just target-framework requirement
Do not reintroduce public GenerateInterceptors documentation.
Do not remove build report details just because they are not suitable for deterministic golden-file comparison. The report is intentionally human- and agent-readable.
Dependency guidance for agents
Do not add new NuGet packages, template engines, formatters, source-generation helper libraries, or build tools unless the maintainer explicitly asks for that dependency.
In particular, do not replace internal code generation with Scriban, Handlebars.Net, CSharpier, or other external templating/formatting tools as an automatic refactoring step.
When improving generated-code quality, prefer internal refactoring first:
CodeWriter
GeneratedCodeSymbolPrinter
InterceptorNamingService
AspectLifetimeResolver
smaller emitter classes
targeted regression tests
If a new dependency seems useful, propose it as a separate design discussion with trade-offs, package impact, versioning risk, analyzer/generator loading risk, and build performance implications. Do not include it in unrelated cleanup or refactoring changes.