diff --git a/Directory.Build.props b/Directory.Build.props
index a892d49..bf97eca 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -3,7 +3,7 @@
- 1.0.0-beta
+ 1.0.1-beta
MIT
diff --git a/LayeredCraft.DecoWeaver.slnx b/LayeredCraft.DecoWeaver.slnx
index 6448925..1f254db 100644
--- a/LayeredCraft.DecoWeaver.slnx
+++ b/LayeredCraft.DecoWeaver.slnx
@@ -41,6 +41,7 @@
+
diff --git a/README.md b/README.md
index cdd9957..b1518eb 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,8 @@ DecoWeaver is a .NET incremental source generator that brings **compile-time dec
- ⚡ **Zero runtime overhead** - No reflection or assembly scanning at startup
- 🎯 **Type-safe** - Catches configuration errors at compile time
- 🚀 **Fast** - Incremental generation with Roslyn
-- 🔧 **Simple** - Just add `[DecoratedBy]` attributes
+- 🔧 **Simple** - Class-level or assembly-level decorator attributes
+- 🌐 **Flexible** - Apply decorators globally or per-implementation
- 📦 **Clean** - Generates readable, debuggable interceptor code
📚 **[View Full Documentation](https://layeredcraft.github.io/decoweaver/)**
@@ -70,6 +71,9 @@ For more examples including open generics, multiple decorators, and ordering, se
## Key Features
+- **Assembly-Level Decorators**: Apply decorators to all implementations from one place with `[assembly: DecorateService(...)]`
+- **Class-Level Decorators**: Apply decorators to specific implementations with `[DecoratedBy]`
+- **Opt-Out Support**: Exclude specific decorators with `[DoNotDecorate]`
- **Multiple Decorators**: Stack multiple decorators with explicit ordering
- **Generic Type Decoration**: Decorate generic types like `IRepository` with open generic decorators
- **Type-Safe**: Compile-time validation catches errors early
diff --git a/docs/api-reference/attributes.md b/docs/api-reference/attributes.md
index db9e6ec..9f40e35 100644
--- a/docs/api-reference/attributes.md
+++ b/docs/api-reference/attributes.md
@@ -284,8 +284,364 @@ public class OrderService : IOrderService { }
[DecoratedBy(typeof(LoggingRepository))]
```
+## DecorateServiceAttribute
+
+Assembly-level attribute for applying decorators to all implementations of a service interface.
+
+### Syntax
+
+```csharp
+[assembly: DecorateService(typeof(TService), typeof(TDecorator))]
+[assembly: DecorateService(typeof(TService), typeof(TDecorator), Order = int)]
+```
+
+### Constructor
+
+```csharp
+public DecorateServiceAttribute(Type serviceType, Type decoratorType)
+```
+
+### Parameters
+
+- `serviceType`: The service interface type (can be open generic like `IRepository<>`)
+- `decoratorType`: The decorator type to apply (can be open generic like `CachingRepository<>`)
+
+### Properties
+
+#### Order
+
+```csharp
+public int Order { get; set; }
+```
+
+Controls the order when multiple assembly-level decorators are applied. Lower values are applied first (closer to the implementation).
+
+**Default**: `0`
+
+### Target
+
+```csharp
+[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
+```
+
+- **Target**: `AttributeTargets.Assembly` - Applied to assemblies
+- **AllowMultiple**: `true` - Multiple decorators can be defined
+- **Inherited**: `false` - Not inherited
+
+### Examples
+
+#### Basic Usage
+
+```csharp
+using DecoWeaver.Attributes;
+
+// Apply logging to all IRepository implementations
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>))]
+```
+
+#### Multiple Decorators
+
+```csharp
+// Apply logging, caching, and metrics to all repositories
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), Order = 1)]
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>), Order = 2)]
+[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>), Order = 3)]
+```
+
+#### Non-Generic Services
+
+```csharp
+// Apply to non-generic service
+[assembly: DecorateService(typeof(IUserService), typeof(LoggingUserService))]
+```
+
+#### Mixed Generic/Non-Generic
+
+```csharp
+// Open generic service, closed generic decorator
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingUserRepository))]
+```
+
+### Behavior
+
+At compile time, DecoWeaver:
+
+1. Discovers all `[assembly: DecorateService(...)]` attributes
+2. Finds DI registrations matching the service type
+3. Merges with class-level `[DecoratedBy]` attributes
+4. Generates interceptor code applying all decorators in order
+
+### Type Matching
+
+Assembly-level decorators match registrations based on:
+
+- **Open generic** `IRepository<>` matches all closed registrations (`IRepository`, `IRepository`, etc.)
+- **Closed generic** `IRepository` matches only `IRepository`
+- **Non-generic** `IUserService` matches exactly
+
+### Combining with Class-Level
+
+Assembly-level and class-level decorators are merged:
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), Order = 10)]
+
+// UserRepository.cs
+[DecoratedBy>(Order = 5)]
+public class UserRepository : IRepository { }
+```
+
+**Resulting chain**: `LoggingRepository` → `ValidationRepository` → `UserRepository`
+
+### Compile-Time Behavior
+
+This attribute is marked with `[Conditional("DECOWEAVER_EMIT_ATTRIBUTE_METADATA")]`, meaning:
+
+- The attribute does **not** exist in the compiled assembly
+- No runtime reflection is possible
+- Zero metadata footprint
+- Only affects compile-time code generation
+
+### Requirements
+
+1. **Decorator must implement service interface**
+2. **Decorator must have constructor accepting interface as first parameter**
+3. **Only intercepts closed generic registrations** (`AddScoped, Impl>()`)
+4. **Does not intercept open generic registrations** (`AddScoped(typeof(IRepo<>), typeof(Impl<>))`)
+
+### Best Practices
+
+1. **Group by concern** - Keep related decorators together
+2. **Use gaps in Order** - Reserve ranges (10-19 for logging, 20-29 for caching, etc.)
+3. **Document your strategy** - Comment why decorators are applied assembly-wide
+4. **Centralize location** - Keep all assembly attributes in `GlobalUsings.cs` or similar
+
+## SkipAssemblyDecorationAttribute
+
+Class-level attribute for opting out of all assembly-level decorators.
+
+### Syntax
+
+```csharp
+[SkipAssemblyDecoration]
+```
+
+### Constructor
+
+```csharp
+public SkipAssemblyDecorationAttribute()
+```
+
+No parameters required.
+
+### Target
+
+```csharp
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+```
+
+- **Target**: `AttributeTargets.Class` - Applied to classes
+- **AllowMultiple**: `false` - Can only be applied once per class
+- **Inherited**: `false` - Not inherited
+
+### Examples
+
+#### Skip All Assembly Decorators
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>))]
+
+// UserRepository.cs - gets all assembly decorators
+public class UserRepository : IRepository { }
+
+// OrderRepository.cs - skips ALL assembly decorators
+[SkipAssemblyDecoration]
+public class OrderRepository : IRepository { }
+```
+
+**Result**:
+- `UserRepository`: Decorated with Logging, Caching, and Metrics
+- `OrderRepository`: No decorators applied
+
+#### Combined with Class-Level Decorators
+
+```csharp
+// Skip assembly decorators, use class-level instead
+[SkipAssemblyDecoration]
+[DecoratedBy>]
+public class ProductRepository : IRepository
+{
+ // Only ValidationRepository is applied (class-level)
+}
+```
+
+### Behavior
+
+At compile time, DecoWeaver:
+
+1. Discovers all `[SkipAssemblyDecoration]` attributes on classes
+2. Excludes ALL assembly-level decorators for that implementation
+3. **Still applies** any class-level `[DecoratedBy]` decorators
+4. Generates interceptor code with only class-level decorators
+
+### Scope
+
+- **Affects**: Only assembly-level `[DecorateService]` decorators
+- **Does NOT affect**: Class-level `[DecoratedBy]` decorators
+- **Isolation**: Only affects the specific class it's applied to
+
+### Use Cases
+
+1. **Performance-critical implementations** - Zero decorator overhead
+2. **Completely different decoration strategy** - Use class-level decorators instead
+3. **Clean slate needed** - Start fresh without assembly defaults
+4. **Legacy code** - Gradual migration to new decoration patterns
+
+### Compile-Time Behavior
+
+This attribute is marked with `[Conditional("DECOWEAVER_EMIT_ATTRIBUTE_METADATA")]`, meaning:
+
+- The attribute does **not** exist in the compiled assembly
+- No runtime reflection is possible
+- Zero metadata footprint
+- Only affects compile-time code generation
+
+### Comparison with DoNotDecorate
+
+| Attribute | Scope | Use When |
+|-----------|-------|----------|
+| `[SkipAssemblyDecoration]` | Removes ALL assembly decorators | Need clean slate or most decorators excluded |
+| `[DoNotDecorate(typeof(...))]` | Removes specific decorator(s) | Need to exclude 1-2 decorators |
+
+### Best Practices
+
+1. **Use for clean slate** - When you want zero assembly decorators
+2. **Combine with class-level** - Apply implementation-specific decorators
+3. **Document why** - Add comments explaining the opt-out reason
+4. **Prefer DoNotDecorate for surgical exclusions** - If only removing 1-2 decorators
+
+## DoNotDecorateAttribute
+
+Class-level attribute for excluding specific decorators from an implementation.
+
+### Syntax
+
+```csharp
+[DoNotDecorate(typeof(TDecorator))]
+```
+
+### Constructor
+
+```csharp
+public DoNotDecorateAttribute(Type decoratorType)
+```
+
+### Parameters
+
+- `decoratorType`: The decorator type to exclude (can be open generic like `CachingRepository<>`)
+
+### Target
+
+```csharp
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
+```
+
+- **Target**: `AttributeTargets.Class` - Applied to classes
+- **AllowMultiple**: `true` - Multiple decorators can be excluded
+- **Inherited**: `false` - Not inherited
+
+### Examples
+
+#### Opt Out of Assembly-Level Decorator
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+
+// UserRepository.cs - gets caching
+public class UserRepository : IRepository { }
+
+// OrderRepository.cs - opts out of caching
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class OrderRepository : IRepository { }
+```
+
+#### Multiple Opt-Outs
+
+```csharp
+// Opt out of caching and metrics
+[DoNotDecorate(typeof(CachingRepository<>))]
+[DoNotDecorate(typeof(MetricsRepository<>))]
+public class OrderRepository : IRepository { }
+```
+
+#### Open Generic Matching
+
+```csharp
+// Open generic in DoNotDecorate matches all closed generics
+[DoNotDecorate(typeof(CachingRepository<>))] // Matches CachingRepository
+public class UserRepository : IRepository { }
+```
+
+### Behavior
+
+At compile time, DecoWeaver:
+
+1. Discovers all `[DoNotDecorate]` attributes on classes
+2. Collects decorators from class-level and assembly-level sources
+3. **Filters out** decorators matching the `DoNotDecorate` directives
+4. Generates interceptor code with only remaining decorators
+
+### Type Matching Rules
+
+- **Open generic** `typeof(Decorator<>)` matches all closed variants
+- **Closed generic** `typeof(Decorator)` matches only that specific type
+- **Non-generic** types match exactly
+- Type matching is by **definition** (ignoring type arguments)
+
+### Isolation
+
+`[DoNotDecorate]` only affects the specific class it's applied to:
+
+```csharp
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class OrderRepository : IRepository { }
+
+// UserRepository still gets caching (not affected)
+public class UserRepository : IRepository { }
+```
+
+### Use Cases
+
+1. **Excluding assembly-level decorators** - Primary use case
+2. **Performance-critical code** - Remove observability overhead
+3. **Implementation-specific constraints** - Special requirements
+4. **Testing implementations** - Simplify test setup
+
+### Compile-Time Behavior
+
+This attribute is marked with `[Conditional("DECOWEAVER_EMIT_ATTRIBUTE_METADATA")]`, meaning:
+
+- The attribute does **not** exist in the compiled assembly
+- No runtime reflection is possible
+- Zero metadata footprint
+- Only affects compile-time code generation
+
+### Best Practices
+
+1. **Use sparingly** - If many implementations opt out, reconsider assembly-level decorator
+2. **Document why** - Comment explaining the opt-out reason
+3. **Exact type match** - Ensure decorator type exactly matches what's being applied
+4. **Prefer class-level for specific needs** - Use assembly-level for cross-cutting concerns
+
## See Also
- [Class-Level Decorators](../usage/class-level-decorators.md) - Usage guide
+- [Assembly-Level Decorators](../usage/assembly-level-decorators.md) - Assembly-wide decoration
+- [Opt-Out](../usage/opt-out.md) - Excluding decorators
- [Multiple Decorators](../usage/multiple-decorators.md) - Stacking decorators
- [Order and Nesting](../core-concepts/order-and-nesting.md) - Understanding order
\ No newline at end of file
diff --git a/docs/changelog.md b/docs/changelog.md
index 68f61dd..1f272dc 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -10,6 +10,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- No changes yet
+## [1.0.1-beta] - 2025-01-XX
+
+### Added
+- Assembly-level `[DecorateService(typeof(TService), typeof(TDecorator))]` attribute for applying decorators to all implementations of a service interface
+- `[SkipAssemblyDecoration]` attribute for opting out of all assembly-level decorators
+- `[DoNotDecorate(typeof(TDecorator))]` attribute for surgically excluding specific decorators from individual implementations
+- Merge/precedence logic for combining class-level and assembly-level decorators
+- Support for ordering assembly-level decorators via `Order` property
+- Open generic matching in assembly-level decorators and DoNotDecorate directives
+- 4 new test cases (029-032) covering assembly-level decorator scenarios
+
+### Changed
+- Decorator discovery pipeline now includes assembly-level attribute streams
+- BuildDecorationMap now merges and deduplicates decorators from multiple sources
+- Documentation restructured to include assembly-level decorator guides
+
+### Technical Details
+- Added `DecorateServiceAttribute` for assembly-level decoration
+- Added `SkipAssemblyDecorationAttribute` for opting out of all assembly decorators
+- Added `DoNotDecorateAttribute` for surgical decorator exclusion
+- Added `ServiceDecoratedByProvider` for assembly attribute discovery
+- Added `SkipAssemblyDecoratorProvider` for skip directive discovery
+- Added `DoNotDecorateProvider` for opt-out directive discovery
+- Filtering logic added to BuildDecorationMap for both skip and do-not-decorate support
+- Comprehensive test coverage with snapshot verification
+
## [1.0.0-beta]
### Added
@@ -103,7 +129,6 @@ Features marked for deprecation:
Planned features for future releases:
### Under Consideration
-- Assembly-level `[DecorateService]` attribute (See [Issue #2](https://github.com/layeredcraft/decoweaver/issues/2))
- Decorator composition helpers
- Performance profiling decorators
- Additional diagnostic analyzers
diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md
index 8172179..b4dd5d1 100644
--- a/docs/getting-started/quick-start.md
+++ b/docs/getting-started/quick-start.md
@@ -119,10 +119,28 @@ To see the generated interceptor code:
**Rider**: Solution Explorer → Generated Files node
+## Alternative: Assembly-Level Decorators
+
+Instead of applying decorators to each class individually, you can apply them to all implementations from one place:
+
+```csharp
+// In GlobalUsings.cs or any .cs file
+using DecoWeaver.Attributes;
+
+[assembly: DecorateService(typeof(IUserService), typeof(LoggingUserService))]
+
+// Now ALL IUserService implementations automatically get logging
+public class UserService : IUserService { }
+public class AdminUserService : IUserService { }
+```
+
+This is ideal for cross-cutting concerns like logging, metrics, or caching that apply to many services. See [Assembly-Level Decorators](../usage/assembly-level-decorators.md) for details.
+
## Next Steps
Now that you have a basic decorator working, explore:
+- **[Assembly-Level Decorators](../usage/assembly-level-decorators.md)** - Apply decorators globally
- **[Multiple Decorators](../usage/multiple-decorators.md)** - Stack decorators with ordering
- **[Open Generics](../usage/open-generics.md)** - Decorate `IRepository` patterns
- **[Examples](../examples/index.md)** - Real-world decorator patterns
diff --git a/docs/index.md b/docs/index.md
index 36f4104..6a33e0b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -10,8 +10,10 @@
- **⚡ Zero Runtime Overhead**: Decorators applied at compile time using C# interceptors
- **🎯 Type-Safe**: Full compile-time validation with IntelliSense support
-- **🔧 Simple API**: Just add `[DecoratedBy]` attributes to your classes
+- **🔧 Simple API**: Apply decorators with `[DecoratedBy]` or `[assembly: DecorateService(...)]`
+- **🌐 Assembly-Level Decorators**: Apply decorators to all implementations from one place
- **🚀 Generic Type Decoration**: Decorate generic types like `IRepository` with open generic decorators
+- **🚫 Opt-Out Support**: Exclude specific decorators with `[DoNotDecorate]`
- **📦 No Runtime Dependencies**: Only build-time source generator dependency
- **🔗 Order Control**: Explicit decorator ordering via `Order` property
- **✨ Clean Generated Code**: Readable, debuggable interceptor code
diff --git a/docs/usage/assembly-level-decorators.md b/docs/usage/assembly-level-decorators.md
new file mode 100644
index 0000000..74976d4
--- /dev/null
+++ b/docs/usage/assembly-level-decorators.md
@@ -0,0 +1,459 @@
+# Assembly-Level Decorators
+
+Assembly-level decorators provide a centralized way to apply decorators to multiple implementations across your codebase. Instead of applying `[DecoratedBy]` to each class individually, you can declare decorators once at the assembly level.
+
+## Basic Syntax
+
+Use the `[assembly: DecorateService(...)]` attribute in any `.cs` file (commonly in `GlobalUsings.cs` or `AssemblyInfo.cs`):
+
+```csharp
+using DecoWeaver.Attributes;
+
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+```
+
+This applies `CachingRepository<>` to **all** implementations of `IRepository<>` registered in the DI container.
+
+## When to Use Assembly-Level Decorators
+
+Assembly-level decorators are ideal for:
+
+### Cross-Cutting Concerns
+
+Apply the same decorator to many implementations:
+
+```csharp
+// In GlobalUsings.cs or AssemblyInfo.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>))]
+```
+
+Now **every** `IRepository` implementation automatically gets logging and metrics.
+
+### Centralized Configuration
+
+Manage all decorators in one place instead of scattered across many classes:
+
+```csharp
+// ❌ Before: Scattered across many files
+[DecoratedBy]
+public class UserRepository : IRepository { }
+
+[DecoratedBy]
+public class ProductRepository : IRepository { }
+
+[DecoratedBy]
+public class OrderRepository : IRepository { }
+
+// ✅ After: Centralized in one place
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>))]
+
+public class UserRepository : IRepository { }
+public class ProductRepository : IRepository { }
+public class OrderRepository : IRepository { }
+```
+
+### Consistency Enforcement
+
+Ensure all implementations follow the same patterns:
+
+```csharp
+// Enforce observability for all repositories
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), Order = 1)]
+[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>), Order = 2)]
+
+// Enforce caching for all query services
+[assembly: DecorateService(typeof(IQueryService<>), typeof(CachingQueryService<>))]
+```
+
+## Syntax Variants
+
+### Open Generic Service and Decorator
+
+Most common - both service and decorator are generic:
+
+```csharp
+[assembly: DecorateService(
+ typeof(IRepository<>), // Service type
+ typeof(CachingRepository<>) // Decorator type
+)]
+```
+
+### Open Generic Service, Closed Generic Decorator
+
+Service is generic, decorator is closed:
+
+```csharp
+[assembly: DecorateService(
+ typeof(IRepository<>), // Service type
+ typeof(CachingUserRepository) // Closed decorator for User only
+)]
+```
+
+This only decorates implementations where T matches the decorator's closed type.
+
+### Non-Generic Service and Decorator
+
+Both service and decorator are concrete:
+
+```csharp
+[assembly: DecorateService(
+ typeof(IUserService),
+ typeof(LoggingUserService)
+)]
+```
+
+## Decorator Requirements
+
+Assembly-level decorators have the same requirements as class-level decorators:
+
+1. **Implement the service interface**
+2. **Accept the interface as constructor parameter** (typically first)
+3. **Have resolvable dependencies** from DI container
+
+```csharp
+public interface IRepository
+{
+ Task GetByIdAsync(int id);
+ Task SaveAsync(T entity);
+}
+
+// ✅ Valid assembly-level decorator
+public class CachingRepository : IRepository
+{
+ private readonly IRepository _inner;
+ private readonly IMemoryCache _cache;
+
+ public CachingRepository(IRepository inner, IMemoryCache cache)
+ {
+ _inner = inner;
+ _cache = cache;
+ }
+
+ public async Task GetByIdAsync(int id)
+ {
+ var key = $"{typeof(T).Name}:{id}";
+ if (_cache.TryGetValue(key, out T cached))
+ return cached;
+
+ var entity = await _inner.GetByIdAsync(id);
+ _cache.Set(key, entity, TimeSpan.FromMinutes(5));
+ return entity;
+ }
+
+ public Task SaveAsync(T entity) => _inner.SaveAsync(entity);
+}
+```
+
+## Multiple Assembly-Level Decorators
+
+Stack multiple decorators using the `Order` property:
+
+```csharp
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), Order = 1)]
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>), Order = 2)]
+[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>), Order = 3)]
+```
+
+**Resulting chain** for any `IRepository`:
+```
+MetricsRepository
+ → CachingRepository
+ → LoggingRepository
+ → [Your Implementation]
+```
+
+Lower `Order` values are closer to the implementation (innermost).
+
+## Combining with Class-Level Decorators
+
+You can combine assembly-level and class-level decorators on the same implementation:
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), Order = 10)]
+[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>), Order = 20)]
+
+// UserRepository.cs
+[DecoratedBy>(Order = 5)]
+public class UserRepository : IRepository
+{
+ // Implementation
+}
+```
+
+**Resulting chain**:
+```
+MetricsRepository // Order 20 (assembly-level)
+ → LoggingRepository // Order 10 (assembly-level)
+ → ValidationRepository // Order 5 (class-level)
+ → UserRepository
+```
+
+### Precedence Rules
+
+When combining decorators:
+
+1. **All decorators are merged** (both class-level and assembly-level)
+2. **Sorted by Order** property (ascending)
+3. **Duplicates are removed** (same decorator type + order)
+4. **Class-level takes precedence** over assembly-level for same decorator
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), Order = 10)]
+
+// UserRepository.cs
+[DecoratedBy>(Order = 10)] // Same type and order
+public class UserRepository : IRepository { }
+```
+
+**Result**: Only **one** `LoggingRepository` is applied (class-level takes precedence).
+
+## Opting Out
+
+DecoWeaver provides two ways to opt out of assembly-level decorators:
+
+### Skip All Assembly Decorators
+
+Use `[SkipAssemblyDecoration]` to completely bypass all assembly-level decorators:
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>))]
+
+// UserRepository.cs - gets all three decorators
+public class UserRepository : IRepository { }
+
+// OrderRepository.cs - skips ALL assembly decorators
+[SkipAssemblyDecoration]
+public class OrderRepository : IRepository { }
+
+// ProductRepository.cs - skips assembly, uses class-level instead
+[SkipAssemblyDecoration]
+[DecoratedBy>]
+public class ProductRepository : IRepository { }
+```
+
+**Result**:
+- `UserRepository`: Caching → Logging → Metrics (all assembly-level)
+- `OrderRepository`: No decorators
+- `ProductRepository`: Only Validation (class-level only)
+
+**When to use**: Performance-critical code, completely different decoration strategy, or clean slate needed.
+
+### Exclude Specific Decorators
+
+Use `[DoNotDecorate]` to surgically remove specific decorators while keeping others:
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>))]
+
+// UserRepository.cs - gets both decorators
+public class UserRepository : IRepository { }
+
+// OrderRepository.cs - opts out of caching only
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class OrderRepository : IRepository { }
+
+// ProductRepository.cs - opts out of both (use SkipAssemblyDecoration instead)
+[DoNotDecorate(typeof(CachingRepository<>))]
+[DoNotDecorate(typeof(LoggingRepository<>))]
+public class ProductRepository : IRepository { }
+```
+
+**When to use**: Opt out of 1-2 specific decorators while keeping the rest.
+
+### Choosing Between Them
+
+| Attribute | Scope | Use When |
+|-----------|-------|----------|
+| `[SkipAssemblyDecoration]` | Removes ALL assembly decorators | Need clean slate or completely different strategy |
+| `[DoNotDecorate(typeof(...))]` | Removes specific decorator(s) | Need to exclude 1-2 decorators |
+
+!!! tip "Best Practice"
+ If you need to exclude most/all decorators, use `[SkipAssemblyDecoration]`. If you need to exclude just a few, use `[DoNotDecorate]`.
+
+See [Opt-Out](opt-out.md) for complete details and more examples.
+
+## Registration
+
+Assembly-level decorators work with any service lifetime:
+
+```csharp
+var services = new ServiceCollection();
+
+// All of these get decorated by assembly-level decorators
+services.AddScoped, UserRepository>();
+services.AddSingleton, ProductRepository>();
+services.AddTransient, OrderRepository>();
+```
+
+DecoWeaver automatically intercepts these registrations and applies the decorators.
+
+## How It Works
+
+At compile time, DecoWeaver:
+
+1. **Discovers** all `[assembly: DecorateService(...)]` attributes
+2. **Finds** DI registration calls like `AddScoped, Impl>()`
+3. **Matches** implementations against service types
+4. **Merges** with any class-level decorators
+5. **Generates** interceptor code that wraps the implementation
+
+No runtime reflection or assembly scanning - everything happens at build time.
+
+## Common Patterns
+
+### Observability for All Services
+
+```csharp
+// Apply logging and metrics to all repositories
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), Order = 1)]
+[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>), Order = 2)]
+
+// Apply to all query services
+[assembly: DecorateService(typeof(IQueryService<>), typeof(LoggingQueryService<>), Order = 1)]
+[assembly: DecorateService(typeof(IQueryService<>), typeof(MetricsQueryService<>), Order = 2)]
+```
+
+### Caching Layer
+
+```csharp
+// Cache all read operations
+[assembly: DecorateService(typeof(IReadRepository<>), typeof(CachingRepository<>))]
+
+// But not write operations (no attribute for IWriteRepository<>)
+```
+
+### Security Layer
+
+```csharp
+// Enforce authorization on all commands
+[assembly: DecorateService(typeof(ICommandHandler<>), typeof(AuthorizationHandler<>), Order = 1)]
+
+// Validate all commands
+[assembly: DecorateService(typeof(ICommandHandler<>), typeof(ValidationHandler<>), Order = 2)]
+```
+
+### Resilience
+
+```csharp
+// Add retry to all external service calls
+[assembly: DecorateService(typeof(IExternalService), typeof(RetryDecorator<>), Order = 1)]
+
+// Add circuit breaker
+[assembly: DecorateService(typeof(IExternalService), typeof(CircuitBreakerDecorator<>), Order = 2)]
+```
+
+## Organization
+
+### Single File Approach
+
+Keep all assembly-level decorators in one file:
+
+```csharp
+// GlobalUsings.cs or AssemblyDecorators.cs
+using DecoWeaver.Attributes;
+
+// Repositories
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), Order = 10)]
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>), Order = 20)]
+
+// Query Services
+[assembly: DecorateService(typeof(IQueryService<>), typeof(LoggingQueryService<>), Order = 10)]
+[assembly: DecorateService(typeof(IQueryService<>), typeof(CachingQueryService<>), Order = 20)]
+
+// Command Handlers
+[assembly: DecorateService(typeof(ICommandHandler<>), typeof(ValidationHandler<>), Order = 10)]
+[assembly: DecorateService(typeof(ICommandHandler<>), typeof(AuthorizationHandler<>), Order = 20)]
+```
+
+### Multiple File Approach
+
+Group by concern:
+
+```csharp
+// Observability.Assembly.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>))]
+
+// Performance.Assembly.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+
+// Security.Assembly.cs
+[assembly: DecorateService(typeof(ICommandHandler<>), typeof(AuthorizationHandler<>))]
+```
+
+## Comparison with Class-Level
+
+| Aspect | Assembly-Level | Class-Level |
+|--------|---------------|-------------|
+| **Scope** | All implementations | Single implementation |
+| **Location** | Global file | On class |
+| **Use Case** | Cross-cutting concerns | Specific needs |
+| **Maintenance** | Centralized | Distributed |
+| **Visibility** | Less obvious | More explicit |
+| **Flexibility** | Can opt-out | Full control |
+
+**When to use each**:
+
+- **Assembly-level**: Cross-cutting concerns (logging, metrics, caching)
+- **Class-level**: Implementation-specific decorators (validation, transformation)
+- **Both**: Combine for layered concerns
+
+## Troubleshooting
+
+### Decorator Not Applied
+
+If your assembly-level decorator isn't being applied:
+
+1. **Verify attribute syntax**: Ensure `[assembly: ...]` at the start
+2. **Check service type match**: Service type must match registration
+3. **Rebuild**: Assembly-level changes require full rebuild
+4. **Check for opt-out**: Verify no `[DoNotDecorate]` on the class
+5. **Verify dependencies**: Ensure decorator dependencies are registered
+
+### Wrong Type Argument
+
+```csharp
+// ❌ Error: Type argument mismatch
+[assembly: DecorateService(typeof(IRepository), typeof(CachingRepository<>))]
+
+// ✅ Fixed: Match generic arity
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+```
+
+### Not Intercepting
+
+Assembly-level decorators only intercept **closed generic registrations**:
+
+```csharp
+// ✅ Intercepted
+services.AddScoped, UserRepository>();
+
+// ❌ NOT intercepted (open generic registration)
+services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
+```
+
+DecoWeaver only intercepts the `AddScoped()` syntax.
+
+## Best Practices
+
+1. **Keep assembly-level for cross-cutting concerns** - Don't overuse
+2. **Document your assembly decorators** - They're less visible than class-level
+3. **Use consistent ordering strategy** - Reserve ranges for each concern (10-19 for logging, 20-29 for caching, etc.)
+4. **Prefer class-level for implementation-specific logic** - More explicit and maintainable
+5. **Group attributes by concern** - Makes it easier to find and modify
+6. **Use DoNotDecorate sparingly** - If many implementations opt out, reconsider assembly-level
+
+## Next Steps
+
+- Learn about [Opt-Out](opt-out.md) with `[DoNotDecorate]`
+- Understand [Order and Nesting](../core-concepts/order-and-nesting.md) in depth
+- See how to combine with [Class-Level Decorators](class-level-decorators.md)
+- Explore [Examples](../examples/index.md) of real-world usage
diff --git a/docs/usage/class-level-decorators.md b/docs/usage/class-level-decorators.md
index c11bf4e..e3f5a21 100644
--- a/docs/usage/class-level-decorators.md
+++ b/docs/usage/class-level-decorators.md
@@ -2,6 +2,9 @@
The `[DecoratedBy]` attribute is the primary way to apply decorators in DecoWeaver. Apply it to your service implementation classes to automatically wrap them with decorators.
+!!! info "Assembly-Level Alternative"
+ For cross-cutting concerns applied to many implementations, consider using [Assembly-Level Decorators](assembly-level-decorators.md) instead. Class-level decorators are best for implementation-specific needs.
+
## Basic Syntax
### Generic Attribute
@@ -381,8 +384,46 @@ public class LoggingDecorator : IUserRepository
4. **Document decorator behavior** with XML comments
5. **Test decorators in isolation** with mocked inner implementations
+## Comparison with Assembly-Level
+
+| Aspect | Class-Level | Assembly-Level |
+|--------|-------------|----------------|
+| **Scope** | Single implementation | All implementations |
+| **Attribute Location** | On class | In global file |
+| **Use Case** | Implementation-specific | Cross-cutting concerns |
+| **Visibility** | More explicit | Less obvious |
+| **Flexibility** | Full control | Can opt-out with `[DoNotDecorate]` |
+
+### When to Use Each
+
+**Use Class-Level When**:
+- Decorator is specific to one implementation
+- You want explicit, visible decoration
+- Different implementations need different decorators
+- Testing or development-only decorators
+
+**Use Assembly-Level When**:
+- Same decorator applies to many implementations
+- Enforcing cross-cutting concerns (logging, metrics, caching)
+- Centralizing decorator configuration
+- Ensuring consistency across implementations
+
+**Combine Both When**:
+- Assembly-level for common concerns
+- Class-level for implementation-specific needs
+
+```csharp
+// GlobalUsings.cs - Common logging for all
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), Order = 10)]
+
+// UserRepository.cs - Add validation specific to users
+[DecoratedBy>(Order = 5)]
+public class UserRepository : IRepository { }
+```
+
## Next Steps
+- Learn about [Assembly-Level Decorators](assembly-level-decorators.md) for cross-cutting concerns
- Learn about [Multiple Decorators](multiple-decorators.md)
- Explore [Open Generic Support](open-generics.md)
- See [Examples](../examples/index.md) of real-world usage
\ No newline at end of file
diff --git a/docs/usage/opt-out.md b/docs/usage/opt-out.md
index dba7315..099091a 100644
--- a/docs/usage/opt-out.md
+++ b/docs/usage/opt-out.md
@@ -1,25 +1,438 @@
# Opt-Out Mechanisms
-!!! info "Coming Soon"
- This documentation is under development. Check back soon for information about opting out of decorator application.
+Sometimes you need to exclude decorators from certain implementations. DecoWeaver provides two opt-out mechanisms:
-## Overview
+- **`[SkipAssemblyDecoration]`** - Opts out of ALL assembly-level decorators
+- **`[DoNotDecorate(typeof(...))]`** - Surgically removes specific decorators
-This page will cover:
+## SkipAssemblyDecoration Attribute
-- How to exclude specific decorators from being applied
-- Global opt-out mechanisms
-- Surgical opt-out for specific scenarios
-- Use cases for opting out of decoration
+Use `[SkipAssemblyDecoration]` to opt out of **all** assembly-level decorators while keeping class-level decorators:
-## Planned Content
+```csharp
+// GlobalUsings.cs - Apply multiple decorators to all repositories
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>))]
-- [ ] Opt-out attribute usage
-- [ ] Per-decorator exclusions
-- [ ] Assembly-level opt-out mechanisms (Phase 2)
-- [ ] Best practices for when to use opt-outs
+// UserRepository.cs - Gets all three assembly-level decorators
+public class UserRepository : IRepository { }
+
+// OrderRepository.cs - Completely opts out of assembly decorators
+[SkipAssemblyDecoration]
+public class OrderRepository : IRepository { }
+
+// ProductRepository.cs - Opts out of assembly, adds class-level
+[SkipAssemblyDecoration]
+[DecoratedBy>]
+public class ProductRepository : IRepository { }
+```
+
+**Result**:
+- `UserRepository`: Logging → Caching → Metrics (all assembly-level)
+- `OrderRepository`: No decorators at all
+- `ProductRepository`: Only ValidationRepository (class-level only)
+
+### When to Use SkipAssemblyDecoration
+
+**Use this when:**
+- The implementation needs to completely bypass all assembly-level decorators
+- You want a "clean slate" to apply only specific class-level decorators
+- The implementation has unique requirements incompatible with standard decorators
+- Performance-critical code that should have zero decorator overhead
+
+**Example - Performance Critical**:
+```csharp
+[assembly: DecorateService(typeof(IService<>), typeof(LoggingService<>))]
+[assembly: DecorateService(typeof(IService<>), typeof(MetricsService<>))]
+
+// High-throughput service - skip all observability
+[SkipAssemblyDecoration]
+public class HighThroughputService : IService { }
+```
+
+## DoNotDecorate Attribute
+
+Sometimes you need to exclude decorators from certain implementations. DecoWeaver provides the `[DoNotDecorate]` attribute for fine-grained control over decorator application.
+
+## Basic Syntax
+
+Use `[DoNotDecorate(typeof(...))]` on an implementation class to exclude a specific decorator:
+
+```csharp
+[DoNotDecorate(typeof(CachingDecorator))]
+public class UserRepository : IUserRepository
+{
+ // This implementation won't be cached
+}
+```
+
+## When to Opt Out
+
+### Excluding Assembly-Level Decorators
+
+The primary use case is opting out of assembly-level decorators:
+
+```csharp
+// GlobalUsings.cs - Apply caching to all repositories
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+
+// UserRepository.cs - Gets caching
+public class UserRepository : IRepository { }
+
+// OrderRepository.cs - Opts out of caching
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class OrderRepository : IRepository { }
+```
+
+**Result**:
+- `UserRepository`: Decorated with `CachingRepository`
+- `OrderRepository`: No decoration applied
+
+### Implementation-Specific Requirements
+
+Some implementations have unique constraints:
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(TransactionRepository<>))]
+
+// Most repositories need transactions
+public class UserRepository : IRepository { }
+
+// But this one manages its own transactions
+[DoNotDecorate(typeof(TransactionRepository<>))]
+public class LegacyRepository : IRepository
+{
+ // Custom transaction management
+}
+```
+
+### Performance-Critical Code
+
+Opt out of observability decorators for hot paths:
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IService<>), typeof(LoggingService<>))]
+[assembly: DecorateService(typeof(IService<>), typeof(MetricsService<>))]
+
+// Normal service - gets logging and metrics
+public class UserService : IService { }
+
+// Performance-critical - minimal overhead
+[DoNotDecorate(typeof(LoggingService<>))]
+[DoNotDecorate(typeof(MetricsService<>))]
+public class HighThroughputService : IService { }
+```
+
+### Testing Implementations
+
+Opt out of decorators in test implementations:
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+
+// Production implementation - gets caching
+public class ProductionRepository : IRepository { }
+
+// Test implementation - no caching needed
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class InMemoryRepository : IRepository { }
+```
+
+## Multiple Opt-Outs
+
+Apply multiple `[DoNotDecorate]` attributes to exclude multiple decorators:
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>))]
+
+// Opt out of caching and metrics, keep logging
+[DoNotDecorate(typeof(CachingRepository<>))]
+[DoNotDecorate(typeof(MetricsRepository<>))]
+public class OrderRepository : IRepository
+{
+ // Gets LoggingRepository only
+}
+```
+
+## Open Generic Matching
+
+`[DoNotDecorate]` works with open generic types and matches all closed variants:
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+
+// Opt out using open generic type
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class UserRepository : IRepository { }
+```
+
+Even though the assembly decorator will try to apply `CachingRepository` (closed generic), the opt-out with `CachingRepository<>` (open generic) matches and excludes it.
+
+**Type Matching Rules**:
+- Open generic `[DoNotDecorate(typeof(Decorator<>))]` matches all closed generics
+- Closed generic `[DoNotDecorate(typeof(Decorator))]` matches only that specific closed type
+- Non-generic types match exactly
+
+## Combining with Class-Level Decorators
+
+`[DoNotDecorate]` can also remove class-level decorators, though this is less common:
+
+```csharp
+// Base class with decorator
+public abstract class BaseRepository : IRepository
+{
+ // ...
+}
+
+// Derived class opts out (though you could just not inherit the attribute)
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class UserRepository : BaseRepository { }
+```
+
+!!! note "Attribute Inheritance"
+ `[DecoratedBy]` attributes are **not inherited**, so this scenario is rare. You'd typically only use `[DoNotDecorate]` to remove assembly-level decorators.
+
+## Isolation Behavior
+
+`[DoNotDecorate]` only affects the **specific implementation** it's applied to:
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+
+// OrderRepository opts out
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class OrderRepository : IRepository { }
+
+// UserRepository still gets caching (not affected)
+public class UserRepository : IRepository { }
+```
+
+Each implementation is evaluated independently.
+
+## How It Works
+
+At compile time, DecoWeaver:
+
+1. **Discovers** all `[DoNotDecorate]` attributes
+2. **Collects** decorators from both class-level and assembly-level
+3. **Filters out** decorators matching the `DoNotDecorate` directives
+4. **Generates** interceptor code with only the remaining decorators
+
+This happens at build time, so there's no runtime overhead.
+
+## Common Patterns
+
+### Selective Caching
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+
+// Read-heavy repositories get caching
+public class ProductRepository : IRepository { }
+public class CategoryRepository : IRepository { }
+
+// Write-heavy repositories opt out
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class OrderRepository : IRepository { }
+
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class InventoryRepository : IRepository { }
+```
+
+### Environment-Specific Decorators
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IService<>), typeof(DetailedLoggingService<>))]
+
+// Most services get detailed logging in dev
+public class UserService : IService { }
+
+// But this one is too noisy
+[DoNotDecorate(typeof(DetailedLoggingService<>))]
+public class ChatService : IService { }
+```
+
+### Gradual Migration
+
+When migrating to assembly-level decorators:
+
+```csharp
+// GlobalUsings.cs - New assembly-level decorator
+[assembly: DecorateService(typeof(IRepository<>), typeof(NewCachingRepository<>))]
+
+// New implementations use the new decorator
+public class UserRepository : IRepository { }
+
+// Legacy implementations opt out (still using old approach)
+[DoNotDecorate(typeof(NewCachingRepository<>))]
+public class LegacyRepository : IRepository
+{
+ // Still using old caching mechanism
+}
+```
+
+### Override Assembly Decisions
+
+Opt out and apply a different decorator:
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(RedisCachingRepository<>))]
+
+// Most repositories use Redis caching
+public class UserRepository : IRepository { }
+
+// This one uses in-memory caching instead
+[DoNotDecorate(typeof(RedisCachingRepository<>))]
+[DecoratedBy>]
+public class ProductRepository : IRepository { }
+```
+
+## Troubleshooting
+
+### Opt-Out Not Working
+
+If `[DoNotDecorate]` isn't removing the decorator:
+
+1. **Verify exact type match**: Type must exactly match the decorator being applied
+2. **Check generic arity**: `CachingRepository<>` vs `CachingRepository`
+3. **Rebuild**: Opt-out changes require full rebuild
+4. **Check assembly-level decorators**: Review what's actually being applied
+
+### Type Name Confusion
+
+```csharp
+// ❌ Wrong: String name doesn't work
+[DoNotDecorate("CachingRepository")]
+
+// ✅ Correct: Use typeof
+[DoNotDecorate(typeof(CachingRepository<>))]
+```
+
+### Namespace Mismatch
+
+```csharp
+// GlobalUsings.cs
+[assembly: DecorateService(typeof(IRepository<>), typeof(Company.Infrastructure.CachingRepository<>))]
+
+// ❌ Wrong: Namespace mismatch
+[DoNotDecorate(typeof(CachingRepository<>))]
+
+// ✅ Correct: Full namespace
+[DoNotDecorate(typeof(Company.Infrastructure.CachingRepository<>))]
+```
+
+Use fully qualified type names when necessary.
+
+## Best Practices
+
+1. **Use sparingly** - If many implementations opt out, reconsider the assembly-level decorator
+2. **Document why** - Add comments explaining the opt-out reason
+3. **Prefer assembly-level for most cases** - Only opt out when truly necessary
+4. **Consider alternatives** - Sometimes a different interface or implementation pattern is better
+5. **Group opt-outs** - If several implementations opt out of the same decorator, consider a marker interface
+
+## Anti-Patterns
+
+### Over-Using Opt-Out
+
+```csharp
+// ❌ Bad: Most implementations opt out
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class Repo1 : IRepository { }
+
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class Repo2 : IRepository { }
+
+// Only one gets caching
+public class Repo3 : IRepository { }
+```
+
+**Better approach**: Remove assembly-level decorator, use class-level where needed.
+
+### Opt-Out as Default
+
+```csharp
+// ❌ Bad: Using opt-out to avoid applying decorators
+[DoNotDecorate(typeof(Decorator1))]
+[DoNotDecorate(typeof(Decorator2))]
+[DoNotDecorate(typeof(Decorator3))]
+public class CleanRepository : IRepository { }
+```
+
+**Better approach**: Don't apply assembly-level decorators if most implementations don't need them.
+
+## Choosing Between Opt-Out Mechanisms
+
+| Attribute | Scope | Use Case |
+|-----------|-------|----------|
+| **`[SkipAssemblyDecoration]`** | Removes ALL assembly decorators | Clean slate, performance critical, completely different decoration strategy |
+| **`[DoNotDecorate(typeof(...))]`** | Removes specific decorator(s) | Opt out of 1-2 decorators while keeping others |
+
+### Decision Tree
+
+```
+Need to opt out?
+├─ Remove ALL assembly decorators?
+│ └─ Use [SkipAssemblyDecoration]
+│
+└─ Remove specific decorator(s)?
+ ├─ Remove 1-3 decorators? → Use [DoNotDecorate]
+ ├─ Remove most decorators? → Use [SkipAssemblyDecoration] + class-level
+ └─ Complex mix? → Reconsider assembly-level approach
+```
+
+### Examples
+
+**Scenario 1: Completely Different Decoration**
+```csharp
+// Most services get standard observability
+[assembly: DecorateService(typeof(IService<>), typeof(LoggingService<>))]
+[assembly: DecorateService(typeof(IService<>), typeof(MetricsService<>))]
+
+// This service has custom observability
+[SkipAssemblyDecoration]
+[DecoratedBy]
+public class SpecialService : IService { }
+```
+
+**Scenario 2: Opt Out of One Decorator**
+```csharp
+[assembly: DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
+[assembly: DecorateService(typeof(IRepository<>), typeof(MetricsRepository<>))]
+
+// Keep logging and metrics, skip caching
+[DoNotDecorate(typeof(CachingRepository<>))]
+public class OrderRepository : IRepository { }
+```
+
+## Comparison: Class-Level vs Assembly-Level
+
+| Approach | When to Use |
+|----------|-------------|
+| **No assembly decorator** | Decorator applies to few implementations |
+| **Assembly decorator** | Decorator applies to most implementations |
+| **Assembly + SkipAssemblyDecoration** | Most use assembly, few need clean slate |
+| **Assembly + DoNotDecorate** | Most use assembly, some need surgical exclusions |
+| **Class-level only** | Implementation-specific decorators |
## See Also
-- [Class-Level Decorators](class-level-decorators.md) - Learn about applying decorators
-- [Multiple Decorators](multiple-decorators.md) - Managing multiple decorators
+- [Assembly-Level Decorators](assembly-level-decorators.md) - Understanding assembly-level decoration
+- [Class-Level Decorators](class-level-decorators.md) - Applying decorators to individual classes
+- [Multiple Decorators](multiple-decorators.md) - Managing decorator chains
+- [API Reference](../api-reference/attributes.md) - Complete attribute documentation
diff --git a/mkdocs.yml b/mkdocs.yml
index 0bbfa2e..2a6d766 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -111,6 +111,7 @@ nav:
- Order & Nesting: core-concepts/order-and-nesting.md
- Usage:
- Class-Level Decorators: usage/class-level-decorators.md
+ - Assembly-Level Decorators: usage/assembly-level-decorators.md
- Open Generics: usage/open-generics.md
- Multiple Decorators: usage/multiple-decorators.md
- Opt-Out: usage/opt-out.md
diff --git a/samples/DecoWeaver.Sample/Globals.cs b/samples/DecoWeaver.Sample/Globals.cs
new file mode 100644
index 0000000..61d3215
--- /dev/null
+++ b/samples/DecoWeaver.Sample/Globals.cs
@@ -0,0 +1,3 @@
+using DecoWeaver.Sample;
+
+[assembly: DecoWeaver.Attributes.DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
diff --git a/samples/DecoWeaver.Sample/Repository.cs b/samples/DecoWeaver.Sample/Repository.cs
index fbb7f07..405a1e0 100644
--- a/samples/DecoWeaver.Sample/Repository.cs
+++ b/samples/DecoWeaver.Sample/Repository.cs
@@ -105,4 +105,49 @@ public void CreateUser(string name)
Console.WriteLine($"[LOG] CreateUser called with name: {name}");
_inner.CreateUser(name);
}
-}
\ No newline at end of file
+}
+
+public interface IAssemblyInterface
+{
+ void DoSomething(T item);
+}
+
+public sealed class ConcreteClass : IAssemblyInterface
+{
+ public void DoSomething(T item)
+ {
+ Console.WriteLine($"Doing something with {item}");
+ }
+}
+
+public sealed class CachingDecorator : IAssemblyInterface
+{
+ private readonly IAssemblyInterface _inner;
+
+ public CachingDecorator(IAssemblyInterface inner)
+ {
+ _inner = inner;
+ }
+
+ public void DoSomething(T item)
+ {
+ Console.WriteLine("Caching before doing something.");
+ _inner.DoSomething(item);
+ }
+}
+
+public sealed class LoggingDecorator : IAssemblyInterface
+{
+ private readonly IAssemblyInterface _inner;
+
+ public LoggingDecorator(IAssemblyInterface inner)
+ {
+ _inner = inner;
+ }
+
+ public void DoSomething(T item)
+ {
+ Console.WriteLine("Logging before doing something.");
+ _inner.DoSomething(item);
+ }
+}
diff --git a/src/LayeredCraft.DecoWeaver.Attributes/DecoratedByAttribute.cs b/src/LayeredCraft.DecoWeaver.Attributes/DecoratedByAttribute.cs
index c8e1028..828fbf0 100644
--- a/src/LayeredCraft.DecoWeaver.Attributes/DecoratedByAttribute.cs
+++ b/src/LayeredCraft.DecoWeaver.Attributes/DecoratedByAttribute.cs
@@ -21,6 +21,12 @@ public sealed class DecoratedByAttribute : Attribute
{
/// Wrapping order; lower numbers are applied first (closest to the implementation).
public int Order { get; set; }
+
+ ///
+ /// Determines if the decorator should be intercepted by the source generator.
+ /// When false, the decorator is present but not automatically applied via code generation.
+ /// Used for decorators applied manually in Program.cs or for documentation purposes.
+ ///
public bool IsInterceptable { get; set; } = true;
}
@@ -42,6 +48,61 @@ public sealed class DecoratedByAttribute(Type decoratorType, int order = 0) : At
/// Wrapping order; lower numbers are applied first.
public int Order { get; set; } = order;
+
+ ///
+ /// Determines if the decorator should be intercepted by the source generator.
+ /// When false, the decorator is present but not automatically applied via code generation.
+ /// Used for decorators applied manually in Program.cs or for documentation purposes.
+ ///
public bool IsInterceptable { get; set; } = true;
}
+///
+/// Declares a decorator to be applied to all registrations of the specified service type
+/// within the containing assembly. Supports open generic definitions (e.g., IRepository<>).
+///
+[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
+[Conditional("DECOWEAVER_EMIT_ATTRIBUTE_METADATA")]
+[EditorBrowsable(EditorBrowsableState.Never)]
+public sealed class DecorateServiceAttribute(Type serviceType, Type decoratorType, int order = 0) : Attribute
+{
+ /// The service type to decorate; must be an interface or base class.
+ public Type ServiceType { get; set; } = serviceType;
+
+ /// The decorator type to apply; must implement the same service contract.
+ public Type DecoratorType { get; set; } = decoratorType;
+
+ /// Wrapping order; lower numbers are applied first.
+ public int Order { get; set; } = order;
+}
+
+///
+/// Opts out an implementation class from all assembly-level decorators in the same assembly.
+/// Use this attribute when a specific implementation should not receive any assembly-level decorations,
+/// but can still have class-level decorations applied.
+///
+///
+/// This attribute only affects assembly-level decorators.
+/// Class-level decorators declared directly on the implementation are still applied.
+///
+[Conditional("DECOWEAVER_EMIT_ATTRIBUTE_METADATA")]
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+public sealed class SkipAssemblyDecorationAttribute() : Attribute;
+
+///
+/// Removes a specific decorator (by type definition) from the merged decoration set.
+/// Use this attribute to surgically remove individual assembly-level decorators without
+/// opting out of all assembly-level decorations.
+///
+///
+/// Matching is performed by type definition, ignoring type arguments. For example,
+/// [DoNotDecorate(typeof(CachingRepository<>))] will remove all closed variants
+/// of CachingRepository from the decoration chain.
+///
+[Conditional("DECOWEAVER_EMIT_ATTRIBUTE_METADATA")]
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
+public sealed class DoNotDecorateAttribute(Type decoratorType) : Attribute
+{
+ /// Decorator type definition to remove (generic definition allowed).
+ public Type DecoratorType { get; set; } = decoratorType;
+}
\ No newline at end of file
diff --git a/src/LayeredCraft.DecoWeaver.Generators/AttributeNames.cs b/src/LayeredCraft.DecoWeaver.Generators/AttributeNames.cs
index 587e8d9..c7f8e4b 100644
--- a/src/LayeredCraft.DecoWeaver.Generators/AttributeNames.cs
+++ b/src/LayeredCraft.DecoWeaver.Generators/AttributeNames.cs
@@ -5,8 +5,14 @@ internal static class AttributeNames
// Full names with namespace (for ToDisplayString() comparisons)
public const string DecoratedByAttribute = $"DecoWeaver.Attributes.{DecoratedByMetadataName}";
public const string GenericDecoratedByAttribute = $"DecoWeaver.Attributes.{GenericDecoratedByMetadataName}";
+ public const string ServiceDecoratedByAttribute = $"DecoWeaver.Attributes.{ServiceDecoratedByMetadataName}";
+ public const string SkipAssemblyDecorationAttribute = $"DecoWeaver.Attributes.{SkipAssemblyDecorationMetadataName}";
+ public const string DoNotDecorateAttribute = $"DecoWeaver.Attributes.{DoNotDecorateMetadataName}";
// Metadata names only (for pattern matching)
public const string DecoratedByMetadataName = "DecoratedByAttribute";
public const string GenericDecoratedByMetadataName = "DecoratedByAttribute`1";
+ public const string ServiceDecoratedByMetadataName = "DecorateServiceAttribute";
+ public const string SkipAssemblyDecorationMetadataName = "SkipAssemblyDecorationAttribute";
+ public const string DoNotDecorateMetadataName = "DoNotDecorateAttribute";
}
\ No newline at end of file
diff --git a/src/LayeredCraft.DecoWeaver.Generators/DecoWeaverGenerator.cs b/src/LayeredCraft.DecoWeaver.Generators/DecoWeaverGenerator.cs
index 8575816..1f6f81a 100644
--- a/src/LayeredCraft.DecoWeaver.Generators/DecoWeaverGenerator.cs
+++ b/src/LayeredCraft.DecoWeaver.Generators/DecoWeaverGenerator.cs
@@ -16,7 +16,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
// --- Language version gate -----------------------------------------
var csharpSufficient = context.CompilationProvider
.Select(static (compilation, _) =>
- compilation is CSharpCompilation { LanguageVersion: LanguageVersion.Default or >= LanguageVersion.CSharp11 })
+ compilation is CSharpCompilation
+ {
+ LanguageVersion: LanguageVersion.Default or >= LanguageVersion.CSharp11
+ })
.WithTrackingName(TrackingNames.Settings_LanguageVersionGate);
context.RegisterSourceOutput(
@@ -34,7 +37,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
AttributeNames.GenericDecoratedByAttribute,
predicate: DecoratedByGenericProvider.Predicate,
transform: DecoratedByGenericProvider.TransformMultiple)
- .SelectMany(static (decorators, _) => decorators.ToImmutableArray()) // Flatten IEnumerable
+ .SelectMany(static (decorators, _) =>
+ decorators.ToImmutableArray()) // Flatten IEnumerable
.WithTrackingName(TrackingNames.Attr_Generic_Transform)
.Where(static x => x is not null)
.WithTrackingName(TrackingNames.Attr_Generic_FilterNotNull)
@@ -48,13 +52,63 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
AttributeNames.DecoratedByAttribute,
predicate: DecoratedByNonGenericProvider.Predicate,
transform: DecoratedByNonGenericProvider.TransformMultiple)
- .SelectMany(static (decorators, _) => decorators.ToImmutableArray()) // Flatten IEnumerable
+ .SelectMany(static (decorators, _) =>
+ decorators.ToImmutableArray()) // Flatten IEnumerable
.WithTrackingName(TrackingNames.Attr_NonGeneric_Transform)
.Where(static x => x is not null)
.WithTrackingName(TrackingNames.Attr_NonGeneric_FilterNotNull)
.Select(static (x, _) => x!.Value)
.WithTrackingName(TrackingNames.Attr_NonGeneric_Stream);
+ // --- [assembly: DecorateService(...)] stream ----------------------
+ // Discovers assembly-level decorator declarations that apply to all implementations
+ // of a service type within the same assembly. This provides default decoration rules
+ // that can be overridden or opted-out of at the class level.
+ var serviceDecorations = context.SyntaxProvider
+ .ForAttributeWithMetadataName(
+ AttributeNames.ServiceDecoratedByAttribute,
+ predicate: ServiceDecoratedByProvider.Predicate,
+ transform: ServiceDecoratedByProvider.TransformMultiple)
+ .SelectMany(static (decorations, _) =>
+ decorations.ToImmutableArray()) // Flatten IEnumerable
+ .WithTrackingName(TrackingNames.Attr_ServiceDecoration_Transform)
+ .Where(static x => x is not null)
+ .WithTrackingName(TrackingNames.Attr_ServiceDecoration_FilterNotNull)
+ .Select(static (x, _) => x!.Value)
+ .WithTrackingName(TrackingNames.Attr_ServiceDecoration_Stream);
+
+ // --- [SkipAssemblyDecorators] stream --------------------------------
+ // Discovers implementations that have opted out of all assembly-level decorations.
+ // These markers are used to filter out assembly-level rules during the merge phase,
+ // while still allowing class-level decorations to be applied.
+ var skipAssemblyDecorations = context.SyntaxProvider
+ .ForAttributeWithMetadataName(
+ AttributeNames.SkipAssemblyDecorationAttribute,
+ predicate: SkipAssemblyDecoratorProvider.Predicate,
+ transform: SkipAssemblyDecoratorProvider.Transform)
+ .WithTrackingName(TrackingNames.Attr_SkipAssemblyDecoration_Transform)
+ .Where(static x => x is not null)
+ .WithTrackingName(TrackingNames.Attr_SkipAssemblyDecoration_FilterNotNull)
+ .Select(static (x, _) => x!.Value)
+ .WithTrackingName(TrackingNames.Attr_SkipAssemblyDecoration_Stream);
+
+ // --- [DoNotDecorate(...)] stream ---------------------------------
+ // Discovers implementations that want to exclude specific decorators from the merged set.
+ // These directives are applied after deduplication but before final emission.
+ // Matching is by TypeDefId (definition only), so open generic decorator types work correctly.
+ var doNotDecorateDirectives = context.SyntaxProvider
+ .ForAttributeWithMetadataName(
+ AttributeNames.DoNotDecorateAttribute,
+ predicate: DoNotDecorateProvider.Predicate,
+ transform: DoNotDecorateProvider.TransformMultiple)
+ .SelectMany(static (directives, _) =>
+ directives.ToImmutableArray()) // Flatten IEnumerable
+ .WithTrackingName(TrackingNames.Attr_DoNotDecorate_Transform)
+ .Where(static x => x is not null)
+ .WithTrackingName(TrackingNames.Attr_DoNotDecorate_FilterNotNull)
+ .Select(static (x, _) => x!.Value)
+ .WithTrackingName(TrackingNames.Attr_DoNotDecorate_Stream);
+
// ✅ Gate each VALUES stream before Collect()
var genericGated = genericDecorations
.Combine(csharpSufficient)
@@ -68,11 +122,34 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.Select(static (pair, _) => pair.Left)
.WithTrackingName(TrackingNames.Gate_Decorations_NonGeneric);
- // Collect both decoration streams
+ var serviceGated = serviceDecorations
+ .Combine(csharpSufficient)
+ .Where(static pair => pair.Right)
+ .Select(static (pair, _) => pair.Left)
+ .WithTrackingName(TrackingNames.Gate_Decorations_Service);
+
+ var skipAssemblyGated = skipAssemblyDecorations
+ .Combine(csharpSufficient)
+ .Where(static pair => pair.Right)
+ .Select(static (pair, _) => pair.Left)
+ .WithTrackingName(TrackingNames.Gate_Decorations_SkipAssembly);
+
+ var doNotDecorateGated = doNotDecorateDirectives
+ .Combine(csharpSufficient)
+ .Where(static pair => pair.Right)
+ .Select(static (pair, _) => pair.Left)
+ .WithTrackingName(TrackingNames.Gate_Decorations_DoNotDecorate);
+
+ // Collect class-level decorations (generic + non-generic combined)
var allDecorations = genericGated.Collect()
.Combine(nonGenericGated.Collect())
.WithTrackingName(TrackingNames.Attr_All_Combined);
+ // Collect assembly-level service decorations separately (to be matched against registrations)
+ var serviceDecosCollected = serviceGated.Collect()
+ .WithTrackingName(TrackingNames.Attr_Service_Collected);
+
+
// --- Closed generic registration discovery -----------------------
var closedGenericRegs = context.SyntaxProvider
.CreateSyntaxProvider(
@@ -91,20 +168,60 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.Collect()
.WithTrackingName(TrackingNames.Reg_ClosedGeneric_Collect);
+ var skipAssemblyCollected = skipAssemblyGated
+ .Collect()
+ .WithTrackingName(TrackingNames.Reg_SkipAssembly_Collect);
+
+ var doNotDecorateCollected = doNotDecorateGated
+ .Collect()
+ .WithTrackingName(TrackingNames.Reg_DoNotDecorate_Collect);
+
+ // Combine all inputs using Select to create a clean named tuple structure
+ var allInputs = allDecorations
+ .Combine(serviceDecosCollected)
+ .Combine(skipAssemblyCollected)
+ .Combine(doNotDecorateCollected)
+ .Combine(closedGenericRegsCollected)
+ .Select(static (data, _) => (
+ GenericDecos: data.Left.Left.Left.Left.Left,
+ NonGenericDecos: data.Left.Left.Left.Left.Right,
+ ServiceDecos: data.Left.Left.Left.Right,
+ SkipMarkers: data.Left.Left.Right,
+ DoNotDecorateDirectives: data.Left.Right,
+ Registrations: data.Right
+ ));
+
// Emit once we have decorations + registrations
context.RegisterSourceOutput(
- allDecorations.Combine(closedGenericRegsCollected)
- .WithTrackingName(TrackingNames.Emit_ClosedGenericInterceptors),
- static (spc, pair) =>
+ allInputs.WithTrackingName(TrackingNames.Emit_ClosedGenericInterceptors),
+ (spc, inputs) =>
{
- var (genericDecos, nonGenericDecos) = pair.Left;
- var regs = pair.Right;
+ // Merge class-level decorations (generic + non-generic)
+ var classDecos = inputs.GenericDecos.Concat(inputs.NonGenericDecos).ToImmutableArray();
- var allDecos = genericDecos.AddRange(nonGenericDecos);
- var byImpl = BuildDecorationMap(allDecos); // Dictionary>
+ // Build a fast lookup of implementations that opted out via [SkipAssemblyDecorators]
+ var skipped = new HashSet(inputs.SkipMarkers.Select(m => m.ImplementationDef));
+
+ // Convert ServiceDecoration → DecoratorToIntercept by matching service types with registrations
+ var assemblyDecos = new List();
+ foreach (var matchingDecos in inputs.Registrations.Select(reg => inputs.ServiceDecos
+ .Where(sd =>
+ sd.ServiceDef.Equals(reg.ServiceDef) && sd.AssemblyName == reg.ImplDef.AssemblyName)
+ .Where(_ => !skipped.Contains(reg.ImplDef))
+ .Select(sd => new DecoratorToIntercept(
+ ImplementationDef: reg.ImplDef,
+ DecoratorDef: sd.DecoratorDef,
+ Order: sd.Order,
+ IsInterceptable: true))))
+ {
+ assemblyDecos.AddRange(matchingDecos);
+ }
+
+ // Build decoration map with proper merge/precedence logic (class-level and assembly-level kept separate)
+ var byImpl = BuildDecorationMap(classDecos, assemblyDecos.ToImmutableArray(), inputs.DoNotDecorateDirectives);
// Only emit interceptors for registrations that have decorators
- var regsWithDecorators = regs
+ var regsWithDecorators = inputs.Registrations
.Where(r => byImpl.TryGetValue(r.ImplDef, out var decos) && decos.Count > 0)
.ToEquatableArray();
@@ -121,24 +238,94 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
}
private static Dictionary> BuildDecorationMap(
- ImmutableArray items)
+ ImmutableArray classDecorators,
+ ImmutableArray assemblyDecorators,
+ ImmutableArray doNotDecorateDirectives)
{
- var tmp = new Dictionary>();
- foreach (var d in items.Where(d => d.IsInterceptable))
+ // Build intermediate structure: ImplementationDef -> List
+ var grouped = new Dictionary>();
+
+ // Add class-level decorators
+ foreach (var d in classDecorators.Where(d => d.IsInterceptable))
+ {
+ if (!grouped.TryGetValue(d.ImplementationDef, out var list))
+ grouped[d.ImplementationDef] = list = new();
+
+ list.Add(new MergedDecoration(
+ DecoratorDef: d.DecoratorDef,
+ Order: d.Order,
+ Source: DecorationSource.Class,
+ IsInterceptable: d.IsInterceptable));
+ }
+
+ // Add assembly-level decorators (always IsInterceptable: true for now)
+ foreach (var d in assemblyDecorators.Where(d => d.IsInterceptable))
+ {
+ if (!grouped.TryGetValue(d.ImplementationDef, out var list))
+ grouped[d.ImplementationDef] = list = new();
+
+ list.Add(new MergedDecoration(
+ DecoratorDef: d.DecoratorDef,
+ Order: d.Order,
+ Source: DecorationSource.Assembly,
+ IsInterceptable: true));
+ }
+
+ // Build fast lookup: ImplementationDef -> Set
+ var exclusions = new Dictionary>();
+ foreach (var directive in doNotDecorateDirectives)
{
- if (!tmp.TryGetValue(d.ImplementationDef, out var list))
- tmp[d.ImplementationDef] = list = new();
+ if (!exclusions.TryGetValue(directive.ImplementationDef, out var excludedSet))
+ exclusions[directive.ImplementationDef] = excludedSet = new HashSet();
- list.Add((d.Order, d.DecoratorDef));
+ excludedSet.Add(directive.DecoratorDef);
}
- var result = new Dictionary>(tmp.Count);
- foreach (var (impl, list) in tmp)
+ // Deduplicate and sort each implementation's decorators
+ var result = new Dictionary>();
+ foreach (var (impl, decorations) in grouped)
{
- list.Sort(static (a, b) => a.Order.CompareTo(b.Order));
- var unique = list.Select(x => x.Deco).Distinct();
- result[impl] = new EquatableArray(unique);
+ // Group by DecoratorDef to find duplicates, then take the one with lowest Source (Class=0 wins over Assembly=1)
+ var deduplicated = decorations
+ .GroupBy(m => m.DecoratorDef)
+ .Select(g => g.MinBy(m => m.Source)!) // Class (0) wins over Assembly (1)
+ .OrderBy(m => m.Order)
+ .ThenBy(m => m.Source)
+ .ThenBy(m => GetSortableTypeName(m.DecoratorDef))
+ .ToList();
+
+ // Apply DoNotDecorateDirective filtering (Priority 3)
+ // Remove decorators whose DecoratorDef matches any exclusion for this implementation
+ if (exclusions.TryGetValue(impl, out var excludedDecorators))
+ {
+ deduplicated = deduplicated
+ .Where(m => !excludedDecorators.Contains(m.DecoratorDef))
+ .ToList();
+ }
+
+ // Extract just the DecoratorDef for emission
+ result[impl] = new EquatableArray(deduplicated.Select(m => m.DecoratorDef));
}
+
return result;
}
+
+ ///
+ /// Generates a stable fully-qualified name for a type definition, used as a tiebreaker
+ /// when sorting decorators with the same Order and Source.
+ ///
+ private static string GetSortableTypeName(TypeDefId typeDefId)
+ {
+ var parts = new List();
+
+ if (typeDefId.ContainingNamespaces.Count > 0)
+ parts.Add(string.Join(".", typeDefId.ContainingNamespaces));
+
+ if (typeDefId.ContainingTypes.Count > 0)
+ parts.Add(string.Join("+", typeDefId.ContainingTypes));
+
+ parts.Add(typeDefId.MetadataName);
+
+ return string.Join(".", parts.Where(p => !string.IsNullOrEmpty(p)));
+ }
}
diff --git a/src/LayeredCraft.DecoWeaver.Generators/Model/DecoratorToIntercept.cs b/src/LayeredCraft.DecoWeaver.Generators/Model/DecoratorToIntercept.cs
index 73e862a..7b5aa41 100644
--- a/src/LayeredCraft.DecoWeaver.Generators/Model/DecoratorToIntercept.cs
+++ b/src/LayeredCraft.DecoWeaver.Generators/Model/DecoratorToIntercept.cs
@@ -10,8 +10,7 @@ internal readonly record struct DecoratorToIntercept(
TypeDefId ImplementationDef, // e.g., SqlRepository`1
TypeDefId DecoratorDef, // e.g., CachingRepository`1
int Order, // default 0
- bool IsInterceptable, // default true; if false, generator should ignore
- LocationId Location
+ bool IsInterceptable
);
/// Lifetime of a DI registration.
public enum DiLifetime : byte { Transient, Scoped, Singleton }
@@ -49,4 +48,57 @@ public readonly record struct RegistrationOccurrence(
TypeId Implementation,
bool WasGenericMethod, // true if AddX, false if AddX(..., typeof(...), typeof(...))
LocationId Location
-);
\ No newline at end of file
+);
+
+///
+/// One assembly-level decoration rule discovered from
+/// [assembly: DecorateService(typeof(Service<>), typeof(Decorator<>), order: ...)].
+/// Applies only within the declaring assembly.
+///
+internal readonly record struct ServiceDecoration(
+ string AssemblyName, // e.g., "LayeredCraft.Data"
+ TypeDefId ServiceDef, // e.g., IRepository`1
+ TypeDefId DecoratorDef, // e.g., CachingRepository`1
+ int Order
+);
+
+///
+/// Marks that a given implementation type has opted out of all assembly-level decorations
+/// via [SkipAssemblyDecorators].
+///
+internal readonly record struct SkipAssemblyDecoratorsMarker(
+ TypeDefId ImplementationDef
+);
+
+///
+/// Removes a specific decorator (by type definition) from the merged set for the given implementation,
+/// discovered from [DoNotDecorate(typeof(Decorator<>))].
+///
+internal readonly record struct DoNotDecorateDirective(
+ TypeDefId ImplementationDef, // where the attribute appears
+ TypeDefId DecoratorDef // definition to remove (generic def allowed)
+);
+
+///
+/// Origin of a decorator in the final merged chain (used for deterministic sorting and precedence).
+///
+internal enum DecorationSource : byte
+{
+ /// Declared directly on the implementation (via [DecoratedBy]).
+ Class = 0,
+
+ /// Declared at the assembly level (via [assembly: DecorateService]).
+ Assembly = 1
+}
+
+///
+/// Unified, already-merged view of a single decorator to emit for a specific registration
+/// (service + implementation). Sorting is by Order asc, then Source asc (Class before Assembly),
+/// then by a stable tiebreaker in the pipeline.
+///
+internal readonly record struct MergedDecoration(
+ TypeDefId DecoratorDef,
+ int Order,
+ DecorationSource Source,
+ bool IsInterceptable // true for class-level unless explicitly disabled; assembly-level defaults to true
+);
diff --git a/src/LayeredCraft.DecoWeaver.Generators/Providers/ClosedGenericRegistrationProvider.cs b/src/LayeredCraft.DecoWeaver.Generators/Providers/ClosedGenericRegistrationProvider.cs
index 99bc288..e0a5614 100644
--- a/src/LayeredCraft.DecoWeaver.Generators/Providers/ClosedGenericRegistrationProvider.cs
+++ b/src/LayeredCraft.DecoWeaver.Generators/Providers/ClosedGenericRegistrationProvider.cs
@@ -68,8 +68,9 @@ internal static bool Predicate(SyntaxNode node, CancellationToken _)
if (il is null) return null;
// Generate fully qualified names for the closed types
- var serviceFqn = $"global::{svc.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted))}";
- var implFqn = $"global::{impl.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted))}";
+ // FullyQualifiedFormat includes global:: for ALL types including nested generic type arguments
+ var serviceFqn = svc.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+ var implFqn = impl.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
return new ClosedGenericRegistration(
ServiceDef: svc.ToTypeId().Definition,
diff --git a/src/LayeredCraft.DecoWeaver.Generators/Providers/DecoratedByGenericProvider.cs b/src/LayeredCraft.DecoWeaver.Generators/Providers/DecoratedByGenericProvider.cs
index 42141d7..35e929a 100644
--- a/src/LayeredCraft.DecoWeaver.Generators/Providers/DecoratedByGenericProvider.cs
+++ b/src/LayeredCraft.DecoWeaver.Generators/Providers/DecoratedByGenericProvider.cs
@@ -56,8 +56,7 @@ internal static bool Predicate(SyntaxNode node, CancellationToken _)
ImplementationDef: implDef.ToTypeId().Definition,
DecoratorDef: decoratorSym.ToTypeId().Definition,
Order: order,
- IsInterceptable: true,
- Location: ctx.TargetNode.ToLocationId());
+ IsInterceptable: true);
}
}
}
\ No newline at end of file
diff --git a/src/LayeredCraft.DecoWeaver.Generators/Providers/DecoratedByNonGenericProvider.cs b/src/LayeredCraft.DecoWeaver.Generators/Providers/DecoratedByNonGenericProvider.cs
index 10267b2..f67c77e 100644
--- a/src/LayeredCraft.DecoWeaver.Generators/Providers/DecoratedByNonGenericProvider.cs
+++ b/src/LayeredCraft.DecoWeaver.Generators/Providers/DecoratedByNonGenericProvider.cs
@@ -47,7 +47,7 @@ internal static bool Predicate(SyntaxNode node, CancellationToken _)
// Order can be either ctor arg #1 (int) or named arg "Order"
var order = AttributeHelpers.GetIntNamedArg(attr, "Order", defaultValue: 0);
- if (order == 0 && attr.ConstructorArguments.Length >= 2 && attr.ConstructorArguments[1].Value is int ctorOrder)
+ if (order == 0 && attr.ConstructorArguments is [_, { Value: int ctorOrder } _, ..])
order = ctorOrder;
// First ctor arg is the Type
@@ -61,8 +61,7 @@ internal static bool Predicate(SyntaxNode node, CancellationToken _)
ImplementationDef: implDef.ToTypeId().Definition,
DecoratorDef: decoratorSym.ToTypeId().Definition,
Order: order,
- IsInterceptable: true,
- Location: ctx.TargetNode.ToLocationId());
+ IsInterceptable: true);
}
}
}
\ No newline at end of file
diff --git a/src/LayeredCraft.DecoWeaver.Generators/Providers/DoNotDecorateProvider.cs b/src/LayeredCraft.DecoWeaver.Generators/Providers/DoNotDecorateProvider.cs
new file mode 100644
index 0000000..9a06e1e
--- /dev/null
+++ b/src/LayeredCraft.DecoWeaver.Generators/Providers/DoNotDecorateProvider.cs
@@ -0,0 +1,53 @@
+using DecoWeaver.Model;
+using DecoWeaver.Roslyn;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace DecoWeaver.Providers;
+
+///
+/// Discovers [DoNotDecorate(typeof(...))] attributes on implementation classes.
+/// These directives exclude specific decorators from the merged decoration chain,
+/// allowing fine-grained control over which decorators apply to each implementation.
+///
+internal static class DoNotDecorateProvider
+{
+ ///
+ /// Filters to classes only (AttributeTargets.Class). ForAttributeWithMetadataName passes all
+ /// node types that could have attributes, so we pre-filter here to avoid semantic analysis on
+ /// structs, interfaces, enums, etc. Decorator pattern requires reference semantics (classes/records),
+ /// not value types (structs/record structs).
+ ///
+ internal static bool Predicate(SyntaxNode node, CancellationToken _)
+ => node is ClassDeclarationSyntax;
+
+ ///
+ /// Processes all [DoNotDecorate] attributes on a class, yielding one DoNotDecorateDirective per attribute.
+ /// This allows multiple decorators to be excluded from the same implementation.
+ ///
+ internal static IEnumerable TransformMultiple(
+ GeneratorAttributeSyntaxContext ctx,
+ CancellationToken ct)
+ {
+ ct.ThrowIfCancellationRequested();
+
+ if (ctx.TargetSymbol is not INamedTypeSymbol implSym)
+ yield break;
+
+ // Process all [DoNotDecorate] attributes on this class (pre-filtered by ForAttributeWithMetadataName)
+ foreach (var attr in ctx.Attributes)
+ {
+ // First ctor arg is the Type to exclude
+ if (attr.ConstructorArguments.Length == 0 || attr.ConstructorArguments[0].Kind != TypedConstantKind.Type)
+ continue;
+
+ var decoratorSym = (ITypeSymbol?)attr.ConstructorArguments[0].Value;
+ if (decoratorSym is null)
+ continue;
+
+ yield return new DoNotDecorateDirective(
+ ImplementationDef: implSym.ToTypeId().Definition,
+ DecoratorDef: decoratorSym.ToTypeId().Definition);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LayeredCraft.DecoWeaver.Generators/Providers/ServiceDecoratedByProvider.cs b/src/LayeredCraft.DecoWeaver.Generators/Providers/ServiceDecoratedByProvider.cs
new file mode 100644
index 0000000..298623b
--- /dev/null
+++ b/src/LayeredCraft.DecoWeaver.Generators/Providers/ServiceDecoratedByProvider.cs
@@ -0,0 +1,72 @@
+using DecoWeaver.Model;
+using DecoWeaver.Roslyn;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace DecoWeaver.Providers;
+
+internal static class ServiceDecoratedByProvider
+{
+ ///
+ /// Filters to classes only (AttributeTargets.Class). ForAttributeWithMetadataName passes all
+ /// node types that could have attributes, so we pre-filter here to avoid semantic analysis on
+ /// structs, interfaces, enums, etc. Decorator pattern requires reference semantics (classes/records),
+ /// not value types (structs/record structs).
+ ///
+ internal static bool Predicate(SyntaxNode node, CancellationToken _)
+ => node is CompilationUnitSyntax cu
+ && cu.AttributeLists.Any(al =>
+ al.Target is { Identifier.ValueText: "assembly" or "module" });
+
+ ///
+ /// Processes all [DecorateService] attributes on an assembly, yielding one ServiceDecoration per attribute.
+ /// These describe decorators that should apply to all implementations of a service type within the assembly.
+ ///
+ internal static IEnumerable TransformMultiple(GeneratorAttributeSyntaxContext ctx,
+ CancellationToken ct)
+ {
+ ct.ThrowIfCancellationRequested();
+
+ if (ctx.TargetSymbol is not IAssemblySymbol asm)
+ yield break;
+
+ // Get the assembly name for scoping the decoration rules
+ var assemblyName = asm.Name;
+
+ // Process all [DecorateService] attributes on this assembly (pre-filtered by ForAttributeWithMetadataName)
+ foreach (var attr in ctx.Attributes)
+ {
+ // Only process DecorateServiceAttribute with pattern matching for namespace
+ if (attr.AttributeClass is not
+ {
+ MetadataName: AttributeNames.ServiceDecoratedByMetadataName,
+ ContainingNamespace:
+ {
+ Name: "Attributes",
+ ContainingNamespace: { Name: "DecoWeaver", ContainingNamespace.IsGlobalNamespace: true }
+ }
+ })
+ continue;
+ // Order can either ctor arg #2 (int) or named arg "Order"
+ var order = AttributeHelpers.GetIntNamedArg(attr, "Order", defaultValue: 0);
+ if (order == 0 && attr.ConstructorArguments is [_, _, { Value: int ctorOrder } _, ..])
+ order = ctorOrder;
+
+ // Constructor args: [0] = service type, [1] = decorator type
+ if (attr.ConstructorArguments.Length < 2 ||
+ attr.ConstructorArguments[0].Kind != TypedConstantKind.Type ||
+ attr.ConstructorArguments[1].Kind != TypedConstantKind.Type)
+ continue;
+
+ var serviceSym = (ITypeSymbol?)attr.ConstructorArguments[0].Value;
+ var decoratorSym = (ITypeSymbol?)attr.ConstructorArguments[1].Value;
+ if (serviceSym is null || decoratorSym is null) continue;
+
+ yield return new ServiceDecoration(
+ AssemblyName: assemblyName,
+ ServiceDef: serviceSym.ToTypeId().Definition,
+ DecoratorDef: decoratorSym.ToTypeId().Definition,
+ Order: order);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LayeredCraft.DecoWeaver.Generators/Providers/SkipAssemblyDecoratorProvider.cs b/src/LayeredCraft.DecoWeaver.Generators/Providers/SkipAssemblyDecoratorProvider.cs
new file mode 100644
index 0000000..e1f3252
--- /dev/null
+++ b/src/LayeredCraft.DecoWeaver.Generators/Providers/SkipAssemblyDecoratorProvider.cs
@@ -0,0 +1,30 @@
+using DecoWeaver.Model;
+using DecoWeaver.Roslyn;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace DecoWeaver.Providers;
+
+internal static class SkipAssemblyDecoratorProvider
+{
+ ///
+ /// Filters to classes only (AttributeTargets.Class). ForAttributeWithMetadataName passes all
+ /// node types that could have attributes, so we pre-filter here to avoid semantic analysis on
+ /// structs, interfaces, enums, etc. Decorator pattern requires reference semantics (classes/records),
+ /// not value types (structs/record structs).
+ ///
+ internal static bool Predicate(SyntaxNode node, CancellationToken _)
+ => node is ClassDeclarationSyntax;
+
+ internal static SkipAssemblyDecoratorsMarker? Transform(GeneratorAttributeSyntaxContext ctx, CancellationToken ct)
+ {
+ ct.ThrowIfCancellationRequested();
+
+ if (ctx.TargetSymbol is not INamedTypeSymbol implDef)
+ return null;
+
+ return new SkipAssemblyDecoratorsMarker(
+ ImplementationDef: implDef.ToTypeId().Definition
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/LayeredCraft.DecoWeaver.Generators/Roslyn/RoslynAdapters.cs b/src/LayeredCraft.DecoWeaver.Generators/Roslyn/RoslynAdapters.cs
index a65bf9b..008825f 100644
--- a/src/LayeredCraft.DecoWeaver.Generators/Roslyn/RoslynAdapters.cs
+++ b/src/LayeredCraft.DecoWeaver.Generators/Roslyn/RoslynAdapters.cs
@@ -6,13 +6,6 @@ namespace DecoWeaver.Roslyn;
internal static class RoslynAdapters
{
- public static LocationId ToLocationId(this SyntaxNode node)
- {
- var span = node.GetLocation().SourceSpan;
- var path = node.SyntaxTree.FilePath;
- return new(path, span.Start, span.Length);
- }
-
public static TypeId ToTypeId(this ITypeSymbol t)
{
switch (t)
diff --git a/src/LayeredCraft.DecoWeaver.Generators/TrackingNames.cs b/src/LayeredCraft.DecoWeaver.Generators/TrackingNames.cs
index 5810b2e..0a9c3da 100644
--- a/src/LayeredCraft.DecoWeaver.Generators/TrackingNames.cs
+++ b/src/LayeredCraft.DecoWeaver.Generators/TrackingNames.cs
@@ -13,10 +13,20 @@ public static class TrackingNames
public const string Attr_NonGeneric_Transform = nameof(Attr_NonGeneric_Transform);
public const string Attr_Generic_FilterNotNull = nameof(Attr_Generic_FilterNotNull);
public const string Attr_NonGeneric_FilterNotNull = nameof(Attr_NonGeneric_FilterNotNull);
+ public const string Attr_ServiceDecoration_Transform = nameof(Attr_ServiceDecoration_Transform);
+ public const string Attr_ServiceDecoration_FilterNotNull = nameof(Attr_ServiceDecoration_FilterNotNull);
+ public const string Attr_ServiceDecoration_Stream = nameof(Attr_ServiceDecoration_Stream);
+ public const string Attr_SkipAssemblyDecoration_Transform = nameof(Attr_SkipAssemblyDecoration_Transform);
+ public const string Attr_SkipAssemblyDecoration_FilterNotNull = nameof(Attr_SkipAssemblyDecoration_FilterNotNull);
+ public const string Attr_SkipAssemblyDecoration_Stream = nameof(Attr_SkipAssemblyDecoration_Stream);
+ public const string Attr_DoNotDecorate_Transform = nameof(Attr_DoNotDecorate_Transform);
+ public const string Attr_DoNotDecorate_FilterNotNull = nameof(Attr_DoNotDecorate_FilterNotNull);
+ public const string Attr_DoNotDecorate_Stream = nameof(Attr_DoNotDecorate_Stream);
public const string Attr_Generic_Stream = nameof(Attr_Generic_Stream);
public const string Attr_NonGeneric_Stream = nameof(Attr_NonGeneric_Stream);
public const string Attr_All_Combined = nameof(Attr_All_Combined);
+ public const string Attr_Service_Collected = nameof(Attr_Service_Collected);
// Gated flow
public const string Gate_Decorations = nameof(Gate_Decorations);
@@ -32,6 +42,8 @@ public static class TrackingNames
public const string Reg_ClosedGeneric_Transform = nameof(Reg_ClosedGeneric_Transform);
public const string Reg_ClosedGeneric_Filter = nameof(Reg_ClosedGeneric_Filter);
public const string Reg_ClosedGeneric_Collect = nameof(Reg_ClosedGeneric_Collect);
+ public const string Reg_SkipAssembly_Collect = nameof(Reg_SkipAssembly_Collect);
+ public const string Reg_DoNotDecorate_Collect = nameof(Reg_DoNotDecorate_Collect);
// Emission
public const string Emit_ClosedGenericInterceptors = nameof(Emit_ClosedGenericInterceptors);
@@ -39,4 +51,7 @@ public static class TrackingNames
// Optional gate per-stream
public const string Gate_Decorations_Generic = nameof(Gate_Decorations_Generic);
public const string Gate_Decorations_NonGeneric = nameof(Gate_Decorations_NonGeneric);
+ public const string Gate_Decorations_Service = nameof(Gate_Decorations_Service);
+ public const string Gate_Decorations_SkipAssembly = nameof(Gate_Decorations_SkipAssembly);
+ public const string Gate_Decorations_DoNotDecorate = nameof(Gate_Decorations_DoNotDecorate);
}
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/023_ServiceDecorator_SingleDecorator/Global.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/023_ServiceDecorator_SingleDecorator/Global.cs
new file mode 100644
index 0000000..47509e7
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/023_ServiceDecorator_SingleDecorator/Global.cs
@@ -0,0 +1,3 @@
+using DecoWeaver.Sample;
+
+[assembly: DecoWeaver.Attributes.DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/023_ServiceDecorator_SingleDecorator/Program.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/023_ServiceDecorator_SingleDecorator/Program.cs
new file mode 100644
index 0000000..a1e4c36
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/023_ServiceDecorator_SingleDecorator/Program.cs
@@ -0,0 +1,33 @@
+using Microsoft.Extensions.DependencyInjection;
+using DecoWeaver.Sample;
+
+// NOTE: DecoWeaver requires closed generic registrations to apply decorators.
+// Open generic registrations like AddScoped(typeof(IRepository<>), typeof(DynamoDbRepository<>))
+// are not supported and will fall through to standard DI registration without decorators.
+var serviceProvider = new ServiceCollection()
+ .AddScoped, DynamoDbRepository>()
+ .AddScoped, DynamoDbRepository>()
+ .BuildServiceProvider();
+
+// Test with Customer repository
+var customerRepo = serviceProvider.GetRequiredService>();
+Console.WriteLine($"Resolved: {customerRepo.GetType().Name}");
+customerRepo.Save(new Customer { Id = 1, Name = "Test Customer" });
+Console.WriteLine();
+
+// Test with Order repository
+var orderRepo = serviceProvider.GetRequiredService>();
+Console.WriteLine($"Resolved: {orderRepo.GetType().Name}");
+orderRepo.Save(new Order { Id = 1, Total = 99.99m });
+
+public class Customer
+{
+ public int Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+}
+
+public class Order
+{
+ public int Id { get; set; }
+ public decimal Total { get; set; }
+}
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/023_ServiceDecorator_SingleDecorator/Repository.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/023_ServiceDecorator_SingleDecorator/Repository.cs
new file mode 100644
index 0000000..41cfbd2
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/023_ServiceDecorator_SingleDecorator/Repository.cs
@@ -0,0 +1,32 @@
+using DecoWeaver.Attributes;
+
+namespace DecoWeaver.Sample;
+
+public interface IRepository
+{
+ void Save(T item);
+}
+
+public sealed class DynamoDbRepository : IRepository
+{
+ public void Save(T item)
+ {
+ Console.WriteLine($"Saving in {nameof(DynamoDbRepository<>)}, type: {typeof(T).Name}");
+ }
+}
+
+public sealed class CachingRepository : IRepository
+{
+ private readonly IRepository _innerRepository;
+
+ public CachingRepository(IRepository innerRepository)
+ {
+ _innerRepository = innerRepository;
+ }
+
+ public void Save(T item)
+ {
+ Console.WriteLine("Saved item to cache.");
+ _innerRepository.Save(item);
+ }
+}
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/024_ServiceDecorator_MultipleOrdered/Global.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/024_ServiceDecorator_MultipleOrdered/Global.cs
new file mode 100644
index 0000000..6061040
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/024_ServiceDecorator_MultipleOrdered/Global.cs
@@ -0,0 +1,4 @@
+using DecoWeaver.Sample;
+
+[assembly: DecoWeaver.Attributes.DecorateService(typeof(IRepository<>), typeof(CachingRepository<>), 1)]
+[assembly: DecoWeaver.Attributes.DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), 2)]
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/024_ServiceDecorator_MultipleOrdered/Program.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/024_ServiceDecorator_MultipleOrdered/Program.cs
new file mode 100644
index 0000000..ff75f94
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/024_ServiceDecorator_MultipleOrdered/Program.cs
@@ -0,0 +1,33 @@
+using Microsoft.Extensions.DependencyInjection;
+using DecoWeaver.Sample;
+
+// Test multiple decorators with explicit order
+// Order 1 (CachingRepository) should be innermost
+// Order 2 (LoggingRepository) should be outermost
+var serviceProvider = new ServiceCollection()
+ .AddScoped, DynamoDbRepository>()
+ .AddScoped, DynamoDbRepository>()
+ .BuildServiceProvider();
+
+// Test with Customer repository
+var customerRepo = serviceProvider.GetRequiredService>();
+Console.WriteLine($"Resolved: {customerRepo.GetType().Name}");
+customerRepo.Save(new Customer { Id = 1, Name = "Test Customer" });
+Console.WriteLine();
+
+// Test with Order repository
+var orderRepo = serviceProvider.GetRequiredService>();
+Console.WriteLine($"Resolved: {orderRepo.GetType().Name}");
+orderRepo.Save(new Order { Id = 1, Total = 99.99m });
+
+public class Customer
+{
+ public int Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+}
+
+public class Order
+{
+ public int Id { get; set; }
+ public decimal Total { get; set; }
+}
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/024_ServiceDecorator_MultipleOrdered/Repository.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/024_ServiceDecorator_MultipleOrdered/Repository.cs
new file mode 100644
index 0000000..4917c2b
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/024_ServiceDecorator_MultipleOrdered/Repository.cs
@@ -0,0 +1,48 @@
+using DecoWeaver.Attributes;
+
+namespace DecoWeaver.Sample;
+
+public interface IRepository
+{
+ void Save(T item);
+}
+
+public sealed class DynamoDbRepository : IRepository
+{
+ public void Save(T item)
+ {
+ Console.WriteLine($"Saving in {nameof(DynamoDbRepository<>)}, type: {typeof(T).Name}");
+ }
+}
+
+public sealed class CachingRepository : IRepository
+{
+ private readonly IRepository _innerRepository;
+
+ public CachingRepository(IRepository innerRepository)
+ {
+ _innerRepository = innerRepository;
+ }
+
+ public void Save(T item)
+ {
+ Console.WriteLine("Saved item to cache.");
+ _innerRepository.Save(item);
+ }
+}
+
+public sealed class LoggingRepository : IRepository
+{
+ private readonly IRepository _innerRepository;
+
+ public LoggingRepository(IRepository innerRepository)
+ {
+ _innerRepository = innerRepository;
+ }
+
+ public void Save(T item)
+ {
+ Console.WriteLine("Logging save operation.");
+ _innerRepository.Save(item);
+ }
+}
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/025_ServiceDecorator_WithSkipAssembly/Global.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/025_ServiceDecorator_WithSkipAssembly/Global.cs
new file mode 100644
index 0000000..47509e7
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/025_ServiceDecorator_WithSkipAssembly/Global.cs
@@ -0,0 +1,3 @@
+using DecoWeaver.Sample;
+
+[assembly: DecoWeaver.Attributes.DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/025_ServiceDecorator_WithSkipAssembly/Program.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/025_ServiceDecorator_WithSkipAssembly/Program.cs
new file mode 100644
index 0000000..3a3e4ee
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/025_ServiceDecorator_WithSkipAssembly/Program.cs
@@ -0,0 +1,31 @@
+using Microsoft.Extensions.DependencyInjection;
+using DecoWeaver.Sample;
+
+// Test skip assembly does not decorate SqlRepository
+var serviceProvider = new ServiceCollection()
+ .AddScoped, DynamoDbRepository>()
+ .AddScoped, SqlRepository>()
+ .BuildServiceProvider();
+
+// Test with Customer repository
+var customerRepo = serviceProvider.GetRequiredService>();
+Console.WriteLine($"Resolved: {customerRepo.GetType().Name}");
+customerRepo.Save(new Customer { Id = 1, Name = "Test Customer" });
+Console.WriteLine();
+
+// Test with Order repository
+var orderRepo = serviceProvider.GetRequiredService>();
+Console.WriteLine($"Resolved: {orderRepo.GetType().Name}");
+orderRepo.Save(new Order { Id = 1, Total = 99.99m });
+
+public class Customer
+{
+ public int Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+}
+
+public class Order
+{
+ public int Id { get; set; }
+ public decimal Total { get; set; }
+}
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/025_ServiceDecorator_WithSkipAssembly/Repository.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/025_ServiceDecorator_WithSkipAssembly/Repository.cs
new file mode 100644
index 0000000..86c0e64
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/025_ServiceDecorator_WithSkipAssembly/Repository.cs
@@ -0,0 +1,41 @@
+using DecoWeaver.Attributes;
+
+namespace DecoWeaver.Sample;
+
+public interface IRepository
+{
+ void Save(T item);
+}
+
+public sealed class DynamoDbRepository : IRepository
+{
+ public void Save(T item)
+ {
+ Console.WriteLine($"Saving in {nameof(DynamoDbRepository<>)}, type: {typeof(T).Name}");
+ }
+}
+
+[SkipAssemblyDecoration]
+public sealed class SqlRepository : IRepository
+{
+ public void Save(T item)
+ {
+ Console.WriteLine($"Saving in {nameof(SqlRepository<>)}, type: {typeof(T).Name}");
+ }
+}
+
+public sealed class CachingRepository : IRepository
+{
+ private readonly IRepository _innerRepository;
+
+ public CachingRepository(IRepository innerRepository)
+ {
+ _innerRepository = innerRepository;
+ }
+
+ public void Save(T item)
+ {
+ Console.WriteLine("Saved item to cache.");
+ _innerRepository.Save(item);
+ }
+}
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/026_SkipAssemblyWithClassLevel/Global.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/026_SkipAssemblyWithClassLevel/Global.cs
new file mode 100644
index 0000000..50c3300
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/026_SkipAssemblyWithClassLevel/Global.cs
@@ -0,0 +1,3 @@
+using DecoWeaver.Attributes;
+
+[assembly: DecorateService(typeof(IRepository<>), typeof(CachingRepository<>), order: 50)]
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/026_SkipAssemblyWithClassLevel/Program.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/026_SkipAssemblyWithClassLevel/Program.cs
new file mode 100644
index 0000000..fd41225
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/026_SkipAssemblyWithClassLevel/Program.cs
@@ -0,0 +1,8 @@
+using Microsoft.Extensions.DependencyInjection;
+
+var services = new ServiceCollection();
+
+// Register SpecialRepository - should get ValidationRepository but NOT CachingRepository
+services.AddScoped, SpecialRepository>();
+
+public class User { }
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/026_SkipAssemblyWithClassLevel/Repository.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/026_SkipAssemblyWithClassLevel/Repository.cs
new file mode 100644
index 0000000..fa506d5
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/026_SkipAssemblyWithClassLevel/Repository.cs
@@ -0,0 +1,26 @@
+using DecoWeaver.Attributes;
+
+public interface IRepository
+{
+ void Save(T entity);
+}
+
+// This repository opts out of assembly-level decorators (CachingRepository)
+// but still has class-level decorators (ValidationRepository) that should apply
+[SkipAssemblyDecoration]
+[DecoratedBy(typeof(ValidationRepository<>), Order = 10)]
+public sealed class SpecialRepository(IRepository inner) : IRepository
+{
+ public void Save(T entity) => inner.Save(entity);
+}
+
+// Decorators
+public sealed class CachingRepository(IRepository inner) : IRepository
+{
+ public void Save(T entity) => inner.Save(entity);
+}
+
+public sealed class ValidationRepository(IRepository inner) : IRepository
+{
+ public void Save(T entity) => inner.Save(entity);
+}
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/027_MergePrecedence_Deduplication/Global.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/027_MergePrecedence_Deduplication/Global.cs
new file mode 100644
index 0000000..05536af
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/027_MergePrecedence_Deduplication/Global.cs
@@ -0,0 +1,5 @@
+using DecoWeaver.Sample;
+
+// Assembly-level: Caching@10 for IRepository<> and Logging@5 for IRepository<>
+[assembly: DecoWeaver.Attributes.DecorateService(typeof(IRepository<>), typeof(CachingRepository<>), 10)]
+[assembly: DecoWeaver.Attributes.DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), 5)]
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/027_MergePrecedence_Deduplication/Program.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/027_MergePrecedence_Deduplication/Program.cs
new file mode 100644
index 0000000..3879492
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/027_MergePrecedence_Deduplication/Program.cs
@@ -0,0 +1,10 @@
+using DecoWeaver.Sample;
+using Microsoft.Extensions.DependencyInjection;
+
+var services = new ServiceCollection();
+
+// Two registrations to verify deduplication works independently per implementation
+services.AddScoped, UserRepository>();
+services.AddScoped, ProductRepository>();
+
+var provider = services.BuildServiceProvider();
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/027_MergePrecedence_Deduplication/Repository.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/027_MergePrecedence_Deduplication/Repository.cs
new file mode 100644
index 0000000..6818eb8
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/027_MergePrecedence_Deduplication/Repository.cs
@@ -0,0 +1,45 @@
+namespace DecoWeaver.Sample;
+
+public interface IRepository
+{
+ T Get(string id);
+ void Save(T entity);
+}
+
+public class User { }
+public class Product { }
+
+// Decorator types
+public class CachingRepository : IRepository
+{
+ private readonly IRepository _inner;
+ public CachingRepository(IRepository inner) => _inner = inner;
+ public T Get(string id) => _inner.Get(id);
+ public void Save(T entity) => _inner.Save(entity);
+}
+
+public class LoggingRepository : IRepository
+{
+ private readonly IRepository _inner;
+ public LoggingRepository(IRepository inner) => _inner = inner;
+ public T Get(string id) => _inner.Get(id);
+ public void Save(T entity) => _inner.Save(entity);
+}
+
+// UserRepository: Assembly declares Caching@10, class declares Caching@20
+// Expected: Deduplication - class-level wins, so only one CachingRepository at order 20
+[DecoWeaver.Attributes.DecoratedBy(typeof(CachingRepository<>), Order = 20)]
+public class UserRepository : IRepository
+{
+ public User Get(string id) => throw new NotImplementedException();
+ public void Save(User entity) => throw new NotImplementedException();
+}
+
+// ProductRepository: Assembly declares Logging@5, class declares Logging@15
+// Expected: Deduplication - class-level wins, so only one LoggingRepository at order 15
+[DecoWeaver.Attributes.DecoratedBy(typeof(LoggingRepository<>), Order = 15)]
+public class ProductRepository : IRepository
+{
+ public Product Get(string id) => throw new NotImplementedException();
+ public void Save(Product entity) => throw new NotImplementedException();
+}
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/028_MergePrecedence_SortOrder/Global.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/028_MergePrecedence_SortOrder/Global.cs
new file mode 100644
index 0000000..e4b7798
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/028_MergePrecedence_SortOrder/Global.cs
@@ -0,0 +1,4 @@
+using DecoWeaver.Sample;
+
+// Assembly-level: Logging@10 for IRepository<>
+[assembly: DecoWeaver.Attributes.DecorateService(typeof(IRepository<>), typeof(LoggingRepository<>), 10)]
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/028_MergePrecedence_SortOrder/Program.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/028_MergePrecedence_SortOrder/Program.cs
new file mode 100644
index 0000000..2d7b35c
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/028_MergePrecedence_SortOrder/Program.cs
@@ -0,0 +1,8 @@
+using DecoWeaver.Sample;
+using Microsoft.Extensions.DependencyInjection;
+
+var services = new ServiceCollection();
+
+services.AddScoped, UserRepository>();
+
+var provider = services.BuildServiceProvider();
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/028_MergePrecedence_SortOrder/Repository.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/028_MergePrecedence_SortOrder/Repository.cs
new file mode 100644
index 0000000..73113af
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/028_MergePrecedence_SortOrder/Repository.cs
@@ -0,0 +1,45 @@
+namespace DecoWeaver.Sample;
+
+public interface IRepository
+{
+ T Get(string id);
+ void Save(T entity);
+}
+
+public class User { }
+
+// Decorator types
+public class CachingRepository : IRepository
+{
+ private readonly IRepository _inner;
+ public CachingRepository(IRepository inner) => _inner = inner;
+ public T Get(string id) => _inner.Get(id);
+ public void Save(T entity) => _inner.Save(entity);
+}
+
+public class LoggingRepository : IRepository
+{
+ private readonly IRepository _inner;
+ public LoggingRepository(IRepository inner) => _inner = inner;
+ public T Get(string id) => _inner.Get(id);
+ public void Save(T entity) => _inner.Save(entity);
+}
+
+public class ValidationRepository : IRepository
+{
+ private readonly IRepository _inner;
+ public ValidationRepository(IRepository inner) => _inner = inner;
+ public T Get(string id) => _inner.Get(id);
+ public void Save(T entity) => _inner.Save(entity);
+}
+
+// UserRepository:
+// Assembly declares: Logging@10
+// Class declares: Validation@10 (same order as assembly-level Logging)
+// Expected sort order: Validation@10 (Class, Source=0), then Logging@10 (Assembly, Source=1)
+[DecoWeaver.Attributes.DecoratedBy(typeof(ValidationRepository<>), Order = 10)]
+public class UserRepository : IRepository
+{
+ public User Get(string id) => throw new NotImplementedException();
+ public void Save(User entity) => throw new NotImplementedException();
+}
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/029_DoNotDecorate_RemovesAssemblyDecorator/Global.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/029_DoNotDecorate_RemovesAssemblyDecorator/Global.cs
new file mode 100644
index 0000000..47509e7
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/029_DoNotDecorate_RemovesAssemblyDecorator/Global.cs
@@ -0,0 +1,3 @@
+using DecoWeaver.Sample;
+
+[assembly: DecoWeaver.Attributes.DecorateService(typeof(IRepository<>), typeof(CachingRepository<>))]
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/029_DoNotDecorate_RemovesAssemblyDecorator/Program.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/029_DoNotDecorate_RemovesAssemblyDecorator/Program.cs
new file mode 100644
index 0000000..12438cb
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/029_DoNotDecorate_RemovesAssemblyDecorator/Program.cs
@@ -0,0 +1,12 @@
+using DecoWeaver.Sample;
+using Microsoft.Extensions.DependencyInjection;
+
+var services = new ServiceCollection();
+
+// DynamoDbRepository SHOULD generate interceptor with CachingRepository
+services.AddScoped, DynamoDbRepository>();
+
+// SqlRepository SHOULD NOT generate interceptor (DoNotDecorate applied)
+services.AddScoped, SqlRepository>();
+
+var provider = services.BuildServiceProvider();
\ No newline at end of file
diff --git a/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/029_DoNotDecorate_RemovesAssemblyDecorator/Repository.cs b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/029_DoNotDecorate_RemovesAssemblyDecorator/Repository.cs
new file mode 100644
index 0000000..60f6f8c
--- /dev/null
+++ b/test/LayeredCraft.DecoWeaver.Generator.Tests/Cases/029_DoNotDecorate_RemovesAssemblyDecorator/Repository.cs
@@ -0,0 +1,34 @@
+namespace DecoWeaver.Sample;
+
+public interface IRepository
+{
+ T Get(string id);
+ void Save(T entity);
+}
+
+public class User { public int Id { get; set; } }
+public class Order { public int Id { get; set; } }
+
+// DynamoDbRepository: Should get decorated (no DoNotDecorate)
+public class DynamoDbRepository : IRepository
+{
+ public T Get(string id) => throw new NotImplementedException();
+ public void Save(T entity) => throw new NotImplementedException();
+}
+
+// SqlRepository: Should NOT be decorated (DoNotDecorate applied)
+[DecoWeaver.Attributes.DoNotDecorate(typeof(CachingRepository<>))]
+public class SqlRepository : IRepository