The Dev-Side DSLs — What We Already Have
"The metadata for complete documentation already exists in your codebase. It is hiding inside attributes that nobody reads for docs."
DSL Anatomy — The Pattern Every DSL Follows
Every DSL in the system follows the same four-part structure:
DSL = Attributes (declare) + Generator (produce) + Analyzer (guard) + .Lib project (package)DSL = Attributes (declare) + Generator (produce) + Analyzer (guard) + .Lib project (package)Cmf.Ddd.Lib/
├── Attributes/
│ ├── AggregateRootAttribute.cs ← declare intent
│ ├── EntityAttribute.cs
│ ├── ValueObjectAttribute.cs
│ ├── CompositionAttribute.cs
│ └── DomainEventAttribute.cs
├── Generators/
│ └── DddGenerator.cs ← produce code (and soon, docs)
├── Analyzers/
│ └── DddAnalyzer.cs ← guard boundaries
└── Cmf.Ddd.Lib.csproj ← package as NuGetCmf.Ddd.Lib/
├── Attributes/
│ ├── AggregateRootAttribute.cs ← declare intent
│ ├── EntityAttribute.cs
│ ├── ValueObjectAttribute.cs
│ ├── CompositionAttribute.cs
│ └── DomainEventAttribute.cs
├── Generators/
│ └── DddGenerator.cs ← produce code (and soon, docs)
├── Analyzers/
│ └── DddAnalyzer.cs ← guard boundaries
└── Cmf.Ddd.Lib.csproj ← package as NuGetA team adds <ProjectReference Include="Cmf.Ddd.Lib" /> to their project and immediately gets: typed attributes to declare domain concepts, source-generated implementations, and compile-time diagnostics when they violate DDD rules. The DSL is mixed into C# code — no separate files, no configuration.
DDD DSL — Architecture Metadata
The DDD DSL provides the structural vocabulary for domain modeling:
// ── Attribute definitions (Cmf.Ddd.Lib) ──────────────────────
[AttributeUsage(AttributeTargets.Class)]
public sealed class AggregateRootAttribute : Attribute
{
public string BoundedContext { get; init; } = "";
public string Description { get; init; } = "";
}
[AttributeUsage(AttributeTargets.Class)]
public sealed class ValueObjectAttribute : Attribute
{
public string Description { get; init; } = "";
}
[AttributeUsage(AttributeTargets.Property)]
public sealed class CompositionAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Method)]
public sealed class DomainEventAttribute : Attribute
{
public Type EventType { get; }
public DomainEventAttribute(Type eventType) => EventType = eventType;
}
// ── Usage (consumer project) ──────────────────────────────────
[AggregateRoot(BoundedContext = "Ordering",
Description = "Represents a customer order with line items and payment")]
public partial class Order
{
[EntityId]
public partial OrderId Id { get; }
[Composition]
public partial IReadOnlyList<OrderLine> Lines { get; }
[Composition]
public partial PaymentInfo Payment { get; }
[DomainEvent(typeof(OrderCreatedEvent))]
public Result Create(CustomerId customer, IReadOnlyList<OrderLineDto> lines)
{
if (lines.Count == 0) return Result.Failure("Order must have at least one line");
// ...
}
[DomainEvent(typeof(OrderPaidEvent))]
public Result MarkAsPaid(PaymentReference reference) { ... }
}
[ValueObject(Description = "Monetary amount with currency")]
public partial record Money(decimal Amount, Currency Currency);
// ── What the SG generates (today) ─────────────────────────────
// Order.g.cs → entity implementation (partial class completion)
// OrderRepository.g.cs → IOrderRepository + EF Core implementation
// OrderConfiguration.g.cs → EF Core IEntityTypeConfiguration<Order>
// ── What the SG COULD generate (with Document DSL) ────────────
// docs/architecture/Order.md → entity diagram, compositions, events
// docs/architecture/Order.mermaid → class diagram with relationships
// docs/architecture/Ordering-context.md → bounded context map// ── Attribute definitions (Cmf.Ddd.Lib) ──────────────────────
[AttributeUsage(AttributeTargets.Class)]
public sealed class AggregateRootAttribute : Attribute
{
public string BoundedContext { get; init; } = "";
public string Description { get; init; } = "";
}
[AttributeUsage(AttributeTargets.Class)]
public sealed class ValueObjectAttribute : Attribute
{
public string Description { get; init; } = "";
}
[AttributeUsage(AttributeTargets.Property)]
public sealed class CompositionAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Method)]
public sealed class DomainEventAttribute : Attribute
{
public Type EventType { get; }
public DomainEventAttribute(Type eventType) => EventType = eventType;
}
// ── Usage (consumer project) ──────────────────────────────────
[AggregateRoot(BoundedContext = "Ordering",
Description = "Represents a customer order with line items and payment")]
public partial class Order
{
[EntityId]
public partial OrderId Id { get; }
[Composition]
public partial IReadOnlyList<OrderLine> Lines { get; }
[Composition]
public partial PaymentInfo Payment { get; }
[DomainEvent(typeof(OrderCreatedEvent))]
public Result Create(CustomerId customer, IReadOnlyList<OrderLineDto> lines)
{
if (lines.Count == 0) return Result.Failure("Order must have at least one line");
// ...
}
[DomainEvent(typeof(OrderPaidEvent))]
public Result MarkAsPaid(PaymentReference reference) { ... }
}
[ValueObject(Description = "Monetary amount with currency")]
public partial record Money(decimal Amount, Currency Currency);
// ── What the SG generates (today) ─────────────────────────────
// Order.g.cs → entity implementation (partial class completion)
// OrderRepository.g.cs → IOrderRepository + EF Core implementation
// OrderConfiguration.g.cs → EF Core IEntityTypeConfiguration<Order>
// ── What the SG COULD generate (with Document DSL) ────────────
// docs/architecture/Order.md → entity diagram, compositions, events
// docs/architecture/Order.mermaid → class diagram with relationships
// docs/architecture/Ordering-context.md → bounded context mapDocumentation metadata in DDD DSL:
BoundedContext→ where this aggregate lives in the architectureDescription→ human-readable summary[Composition]→ relationship to child entities/value objects[DomainEvent]→ what events this aggregate publishes[EntityId]→ identity type
Every property is a field in the generated architecture documentation.
Requirements DSL — Traceability Metadata
// ── Attribute definitions (Cmf.Requirements.Lib) ──────────────
[AttributeUsage(AttributeTargets.Class)]
public sealed class FeatureAttribute : Attribute
{
public string Description { get; init; } = "";
public Priority Priority { get; init; } = Priority.Medium;
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class AcceptanceCriteriaAttribute : Attribute
{
public string Description { get; }
public AcceptanceCriteriaAttribute(string description) => Description = description;
}
public enum Priority { Critical, High, Medium, Low }
// ── Usage (consumer project) ──────────────────────────────────
[Feature(Description = "Process customer orders with payment and fulfillment",
Priority = Priority.Critical)]
public abstract class OrderProcessingFeature : FeatureBase
{
[AcceptanceCriteria("Order can be created with at least one line item")]
public abstract Result OrderCanBeCreated();
[AcceptanceCriteria("Order total is calculated from line quantities × unit prices")]
public abstract Result TotalIsCalculated();
[AcceptanceCriteria("Order can be marked as paid with a valid payment reference")]
public abstract Result OrderCanBePaid();
[AcceptanceCriteria("Paid order triggers fulfillment workflow")]
public abstract Result PaidOrderTriggersFulfillment();
}
// ── What the SG generates (today) ─────────────────────────────
// RequirementRegistry.g.cs → const strings for all requirements
// RequirementConstants.g.cs → FEATURE_ORDER_PROCESSING, etc.
// ── Documentation metadata available ──────────────────────────
// Feature name, description, priority
// 4 acceptance criteria with descriptions
// Each AC is an abstract method → typed, navigable, refactorable
// The feature CLASS is the spec. The abstract methods ARE the ACs.// ── Attribute definitions (Cmf.Requirements.Lib) ──────────────
[AttributeUsage(AttributeTargets.Class)]
public sealed class FeatureAttribute : Attribute
{
public string Description { get; init; } = "";
public Priority Priority { get; init; } = Priority.Medium;
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class AcceptanceCriteriaAttribute : Attribute
{
public string Description { get; }
public AcceptanceCriteriaAttribute(string description) => Description = description;
}
public enum Priority { Critical, High, Medium, Low }
// ── Usage (consumer project) ──────────────────────────────────
[Feature(Description = "Process customer orders with payment and fulfillment",
Priority = Priority.Critical)]
public abstract class OrderProcessingFeature : FeatureBase
{
[AcceptanceCriteria("Order can be created with at least one line item")]
public abstract Result OrderCanBeCreated();
[AcceptanceCriteria("Order total is calculated from line quantities × unit prices")]
public abstract Result TotalIsCalculated();
[AcceptanceCriteria("Order can be marked as paid with a valid payment reference")]
public abstract Result OrderCanBePaid();
[AcceptanceCriteria("Paid order triggers fulfillment workflow")]
public abstract Result PaidOrderTriggersFulfillment();
}
// ── What the SG generates (today) ─────────────────────────────
// RequirementRegistry.g.cs → const strings for all requirements
// RequirementConstants.g.cs → FEATURE_ORDER_PROCESSING, etc.
// ── Documentation metadata available ──────────────────────────
// Feature name, description, priority
// 4 acceptance criteria with descriptions
// Each AC is an abstract method → typed, navigable, refactorable
// The feature CLASS is the spec. The abstract methods ARE the ACs.Testing DSL — Coverage Metadata
// ── Attribute definitions (Cmf.Testing.Lib) ───────────────────
[AttributeUsage(AttributeTargets.Class)]
public sealed class ForRequirementAttribute : Attribute
{
public Type RequirementType { get; }
public ForRequirementAttribute(Type requirementType) => RequirementType = requirementType;
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class VerifiesAttribute : Attribute
{
public Type FeatureType { get; }
public string AcceptanceCriteria { get; }
public VerifiesAttribute(Type featureType, string ac)
{
FeatureType = featureType;
AcceptanceCriteria = ac;
}
}
// ── Usage (test project) ──────────────────────────────────────
[ForRequirement(typeof(OrderProcessingFeature))]
public sealed class OrderProcessingTests
{
[Fact]
[Verifies(typeof(OrderProcessingFeature),
nameof(OrderProcessingFeature.OrderCanBeCreated))]
public void Order_can_be_created_with_valid_lines()
{
var order = Order.Create(customerId, new[] { validLine });
order.IsSuccess.Should().BeTrue();
order.Value.Lines.Should().HaveCount(1);
}
[Fact]
[Verifies(typeof(OrderProcessingFeature),
nameof(OrderProcessingFeature.OrderCanBeCreated))]
public void Order_creation_fails_with_empty_lines()
{
var order = Order.Create(customerId, Array.Empty<OrderLineDto>());
order.IsFailure.Should().BeTrue();
}
[Fact]
[Verifies(typeof(OrderProcessingFeature),
nameof(OrderProcessingFeature.TotalIsCalculated))]
public void Order_total_equals_sum_of_line_totals()
{
var order = Order.Create(customerId, new[] { line10, line20 });
order.Value.Total.Should().Be(new Money(30m, Currency.EUR));
}
// AC3 (OrderCanBePaid) → 2 tests
// AC4 (PaidOrderTriggersFulfillment) → 0 tests ← gap!
}
// ── Documentation metadata available ──────────────────────────
// typeof(OrderProcessingFeature) → which feature this test class covers
// nameof(...OrderCanBeCreated) → which specific AC each test verifies
// Count of [Verifies] per AC → test coverage per acceptance criteria
// Missing: AC4 has no [Verifies] → coverage gap detected// ── Attribute definitions (Cmf.Testing.Lib) ───────────────────
[AttributeUsage(AttributeTargets.Class)]
public sealed class ForRequirementAttribute : Attribute
{
public Type RequirementType { get; }
public ForRequirementAttribute(Type requirementType) => RequirementType = requirementType;
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class VerifiesAttribute : Attribute
{
public Type FeatureType { get; }
public string AcceptanceCriteria { get; }
public VerifiesAttribute(Type featureType, string ac)
{
FeatureType = featureType;
AcceptanceCriteria = ac;
}
}
// ── Usage (test project) ──────────────────────────────────────
[ForRequirement(typeof(OrderProcessingFeature))]
public sealed class OrderProcessingTests
{
[Fact]
[Verifies(typeof(OrderProcessingFeature),
nameof(OrderProcessingFeature.OrderCanBeCreated))]
public void Order_can_be_created_with_valid_lines()
{
var order = Order.Create(customerId, new[] { validLine });
order.IsSuccess.Should().BeTrue();
order.Value.Lines.Should().HaveCount(1);
}
[Fact]
[Verifies(typeof(OrderProcessingFeature),
nameof(OrderProcessingFeature.OrderCanBeCreated))]
public void Order_creation_fails_with_empty_lines()
{
var order = Order.Create(customerId, Array.Empty<OrderLineDto>());
order.IsFailure.Should().BeTrue();
}
[Fact]
[Verifies(typeof(OrderProcessingFeature),
nameof(OrderProcessingFeature.TotalIsCalculated))]
public void Order_total_equals_sum_of_line_totals()
{
var order = Order.Create(customerId, new[] { line10, line20 });
order.Value.Total.Should().Be(new Money(30m, Currency.EUR));
}
// AC3 (OrderCanBePaid) → 2 tests
// AC4 (PaidOrderTriggersFulfillment) → 0 tests ← gap!
}
// ── Documentation metadata available ──────────────────────────
// typeof(OrderProcessingFeature) → which feature this test class covers
// nameof(...OrderCanBeCreated) → which specific AC each test verifies
// Count of [Verifies] per AC → test coverage per acceptance criteria
// Missing: AC4 has no [Verifies] → coverage gap detectedAPI DSL — Contract Metadata
// ── Attribute definitions (Cmf.Api.Lib) ───────────────────────
[AttributeUsage(AttributeTargets.Class)]
public sealed class TypedEndpointAttribute : Attribute
{
public string Method { get; }
public string Route { get; }
public string Description { get; init; } = "";
public string Version { get; init; } = "v1";
public TypedEndpointAttribute(string method, string route)
{
Method = method;
Route = route;
}
}
[AttributeUsage(AttributeTargets.Class)]
public sealed class AuthorizedAttribute : Attribute
{
public string[] Roles { get; init; } = [];
public string Policy { get; init; } = "";
}
// ── Usage (API project) ───────────────────────────────────────
[TypedEndpoint("POST", "/api/orders",
Description = "Create a new order from line items",
Version = "v2")]
[Authorized(Roles = ["OrderManager", "Admin"])]
[ForRequirement(typeof(OrderProcessingFeature))]
public partial class CreateOrderEndpoint
{
public record Request(CustomerId CustomerId, IReadOnlyList<OrderLineDto> Lines);
public record Response(OrderId Id, Money Total);
}
[TypedEndpoint("POST", "/api/orders/{id}/pay",
Description = "Mark an order as paid")]
[Authorized(Roles = ["PaymentProcessor"])]
[ForRequirement(typeof(OrderProcessingFeature))]
public partial class PayOrderEndpoint
{
public record Request(OrderId Id, PaymentReference Reference);
public record Response(OrderId Id, OrderStatus Status);
}
// ── What the SG generates (today) ─────────────────────────────
// CreateOrderEndpoint.g.cs → Minimal API route registration
// PayOrderEndpoint.g.cs → Minimal API route registration
// OpenApiSpec.g.json → OpenAPI 3.0 specification fragment
// ── Documentation metadata available ──────────────────────────
// HTTP method + route → API surface
// Request/Response records → typed contracts
// Roles/Policy → authorization matrix
// typeof(OrderProcessingFeature) → which feature this endpoint serves
// Version → API versioning documentation// ── Attribute definitions (Cmf.Api.Lib) ───────────────────────
[AttributeUsage(AttributeTargets.Class)]
public sealed class TypedEndpointAttribute : Attribute
{
public string Method { get; }
public string Route { get; }
public string Description { get; init; } = "";
public string Version { get; init; } = "v1";
public TypedEndpointAttribute(string method, string route)
{
Method = method;
Route = route;
}
}
[AttributeUsage(AttributeTargets.Class)]
public sealed class AuthorizedAttribute : Attribute
{
public string[] Roles { get; init; } = [];
public string Policy { get; init; } = "";
}
// ── Usage (API project) ───────────────────────────────────────
[TypedEndpoint("POST", "/api/orders",
Description = "Create a new order from line items",
Version = "v2")]
[Authorized(Roles = ["OrderManager", "Admin"])]
[ForRequirement(typeof(OrderProcessingFeature))]
public partial class CreateOrderEndpoint
{
public record Request(CustomerId CustomerId, IReadOnlyList<OrderLineDto> Lines);
public record Response(OrderId Id, Money Total);
}
[TypedEndpoint("POST", "/api/orders/{id}/pay",
Description = "Mark an order as paid")]
[Authorized(Roles = ["PaymentProcessor"])]
[ForRequirement(typeof(OrderProcessingFeature))]
public partial class PayOrderEndpoint
{
public record Request(OrderId Id, PaymentReference Reference);
public record Response(OrderId Id, OrderStatus Status);
}
// ── What the SG generates (today) ─────────────────────────────
// CreateOrderEndpoint.g.cs → Minimal API route registration
// PayOrderEndpoint.g.cs → Minimal API route registration
// OpenApiSpec.g.json → OpenAPI 3.0 specification fragment
// ── Documentation metadata available ──────────────────────────
// HTTP method + route → API surface
// Request/Response records → typed contracts
// Roles/Policy → authorization matrix
// typeof(OrderProcessingFeature) → which feature this endpoint serves
// Version → API versioning documentationThe Documentation Gap
Here is the problem: every DSL generates code from attributes. None generates documentation.
| DSL | SG Generates Today | Could Also Generate |
|---|---|---|
| DDD | Entity .g.cs, Repository .g.cs, EF Config .g.cs |
Architecture diagram, entity relationship docs |
| Requirements | Registry .g.cs, Constants .g.cs |
Traceability matrix, AC table, coverage report |
| Testing | (no SG — attributes are read at test time) | Test coverage per AC, gap analysis |
| API | Endpoint .g.cs, OpenAPI .g.json |
API docs with requirement links, auth matrix |
The metadata is there. It is rich, typed, validated by analyzers. It just needs a reader that produces documentation instead of code. That reader is the Document DSL (Part III).
The Artifact Graph — Find All Consumers Automatically
Before the Document DSL can generate documentation, it needs to answer one question: "What touches this artifact?"
Given Order, the graph must find:
- Which feature requires it →
OrderProcessingFeature - Which tests verify it →
OrderProcessingTests(4 test methods) - Which endpoints expose it →
CreateOrderEndpoint,PayOrderEndpoint - Which deployment steps migrate it → (not yet typed — see Parts IV–V)
This is the Artifact Graph — a Roslyn-built data structure that maps every DSL artifact to every other artifact that references it:
// ── The Graph Model ───────────────────────────────────────────
public sealed record ArtifactNode(
string TypeName,
string FilePath,
int Line,
ArtifactKind Kind);
public enum ArtifactKind
{
Requirement, // [Feature], [Story], [Epic]
Specification, // abstract AC methods
Implementation, // [AggregateRoot], [Entity], service classes
Test, // [Verifies], [ForRequirement] on test classes
Endpoint, // [TypedEndpoint]
Deployment, // [DeploymentOrchestrator], [MigrationStep]
Observation // [HealthCheck], [Metric], [AlertRule]
}
public sealed record ArtifactEdge(
ArtifactNode Source,
ArtifactNode Target,
EdgeKind Relation);
public enum EdgeKind
{
Implements, // code → requirement
Verifies, // test → requirement AC
Serves, // endpoint → requirement
Deploys, // deployment step → implementation
Observes, // health check → app
Configures, // config transform → app
RollsBack // rollback → migration step
}
public sealed class ArtifactGraph
{
private readonly ImmutableArray<ArtifactNode> _nodes;
private readonly ImmutableArray<ArtifactEdge> _edges;
/// Find everything related to a feature
public TraceabilityChain GetChain(Type featureType)
{
// Walks: Feature → Specs (ACs) → Implementations → Tests → Endpoints → Deployments
// Returns the complete chain for documentation
}
/// Find artifacts with DSL attributes but no incoming edges from a Requirement
public IReadOnlyList<ArtifactNode> FindOrphans()
{
// [AggregateRoot] Order exists but no [ForRequirement] links to any feature
// → orphaned implementation, possibly undocumented
}
/// Impact analysis: what is affected if this requirement changes?
public IReadOnlyList<ArtifactNode> GetImpact(Type requirementType)
{
// Returns all implementations, tests, endpoints, deployments
// that would need updating
}
}// ── The Graph Model ───────────────────────────────────────────
public sealed record ArtifactNode(
string TypeName,
string FilePath,
int Line,
ArtifactKind Kind);
public enum ArtifactKind
{
Requirement, // [Feature], [Story], [Epic]
Specification, // abstract AC methods
Implementation, // [AggregateRoot], [Entity], service classes
Test, // [Verifies], [ForRequirement] on test classes
Endpoint, // [TypedEndpoint]
Deployment, // [DeploymentOrchestrator], [MigrationStep]
Observation // [HealthCheck], [Metric], [AlertRule]
}
public sealed record ArtifactEdge(
ArtifactNode Source,
ArtifactNode Target,
EdgeKind Relation);
public enum EdgeKind
{
Implements, // code → requirement
Verifies, // test → requirement AC
Serves, // endpoint → requirement
Deploys, // deployment step → implementation
Observes, // health check → app
Configures, // config transform → app
RollsBack // rollback → migration step
}
public sealed class ArtifactGraph
{
private readonly ImmutableArray<ArtifactNode> _nodes;
private readonly ImmutableArray<ArtifactEdge> _edges;
/// Find everything related to a feature
public TraceabilityChain GetChain(Type featureType)
{
// Walks: Feature → Specs (ACs) → Implementations → Tests → Endpoints → Deployments
// Returns the complete chain for documentation
}
/// Find artifacts with DSL attributes but no incoming edges from a Requirement
public IReadOnlyList<ArtifactNode> FindOrphans()
{
// [AggregateRoot] Order exists but no [ForRequirement] links to any feature
// → orphaned implementation, possibly undocumented
}
/// Impact analysis: what is affected if this requirement changes?
public IReadOnlyList<ArtifactNode> GetImpact(Type requirementType)
{
// Returns all implementations, tests, endpoints, deployments
// that would need updating
}
}Building the Graph with Roslyn
The graph is built by a DiagnosticAnalyzer that walks all symbol references in the compilation:
// ── Graph Builder (runs during compilation) ───────────────────
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ArtifactGraphAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor DOC001 = new(
"DOC001",
"Orphaned artifact",
"'{0}' has [{1}] but is not linked to any requirement via [ForRequirement]",
"Documentation",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public override void Initialize(AnalysisContext context)
{
context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType);
}
private void AnalyzeNamedType(SymbolAnalysisContext context)
{
var symbol = (INamedTypeSymbol)context.Symbol;
// Has a DSL attribute (AggregateRoot, TypedEndpoint, etc.)?
var dslAttributes = symbol.GetAttributes()
.Where(a => IsDslAttribute(a.AttributeClass));
if (!dslAttributes.Any()) return;
// Has a [ForRequirement] link?
var hasRequirementLink = symbol.GetAttributes()
.Any(a => a.AttributeClass?.Name == "ForRequirementAttribute");
if (!hasRequirementLink)
{
// DOC001: Orphaned artifact
context.ReportDiagnostic(Diagnostic.Create(
DOC001, symbol.Locations[0],
symbol.Name,
dslAttributes.First().AttributeClass!.Name));
}
}
}// ── Graph Builder (runs during compilation) ───────────────────
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ArtifactGraphAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor DOC001 = new(
"DOC001",
"Orphaned artifact",
"'{0}' has [{1}] but is not linked to any requirement via [ForRequirement]",
"Documentation",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public override void Initialize(AnalysisContext context)
{
context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType);
}
private void AnalyzeNamedType(SymbolAnalysisContext context)
{
var symbol = (INamedTypeSymbol)context.Symbol;
// Has a DSL attribute (AggregateRoot, TypedEndpoint, etc.)?
var dslAttributes = symbol.GetAttributes()
.Where(a => IsDslAttribute(a.AttributeClass));
if (!dslAttributes.Any()) return;
// Has a [ForRequirement] link?
var hasRequirementLink = symbol.GetAttributes()
.Any(a => a.AttributeClass?.Name == "ForRequirementAttribute");
if (!hasRequirementLink)
{
// DOC001: Orphaned artifact
context.ReportDiagnostic(Diagnostic.Create(
DOC001, symbol.Locations[0],
symbol.Name,
dslAttributes.First().AttributeClass!.Name));
}
}
}Usage — the analyzer fires at compile time:
warning DOC001: 'PaymentGatewayClient' has [Injectable] but is not linked to any
requirement via [ForRequirement]
— consider adding [ForRequirement(typeof(...))] for traceabilitywarning DOC001: 'PaymentGatewayClient' has [Injectable] but is not linked to any
requirement via [ForRequirement]
— consider adding [ForRequirement(typeof(...))] for traceabilityQuerying the Graph
// "Show me everything related to OrderProcessingFeature"
var chain = graph.GetChain(typeof(OrderProcessingFeature));
// chain.Requirements → OrderProcessingFeature (1 feature)
// chain.Criteria → 4 acceptance criteria (abstract methods)
// chain.Implementations → Order aggregate, OrderLine value object
// chain.Tests → 4 test methods across OrderProcessingTests
// chain.Endpoints → CreateOrderEndpoint, PayOrderEndpoint
// chain.Deployments → (empty — not yet typed, see Parts IV–V)
// Coverage analysis
chain.CoverageByAC:
// OrderCanBeCreated → 2 tests ✓
// TotalIsCalculated → 1 test ✓
// OrderCanBePaid → 2 tests ✓
// PaidOrderTriggersFulfillment → 0 tests ✗ ← gap!
// Impact analysis: "What breaks if we change OrderProcessingFeature?"
var impact = graph.GetImpact(typeof(OrderProcessingFeature));
// → Order, OrderLine, OrderProcessingTests, CreateOrderEndpoint, PayOrderEndpoint
// → 5 types across 3 projects need review// "Show me everything related to OrderProcessingFeature"
var chain = graph.GetChain(typeof(OrderProcessingFeature));
// chain.Requirements → OrderProcessingFeature (1 feature)
// chain.Criteria → 4 acceptance criteria (abstract methods)
// chain.Implementations → Order aggregate, OrderLine value object
// chain.Tests → 4 test methods across OrderProcessingTests
// chain.Endpoints → CreateOrderEndpoint, PayOrderEndpoint
// chain.Deployments → (empty — not yet typed, see Parts IV–V)
// Coverage analysis
chain.CoverageByAC:
// OrderCanBeCreated → 2 tests ✓
// TotalIsCalculated → 1 test ✓
// OrderCanBePaid → 2 tests ✓
// PaidOrderTriggersFulfillment → 0 tests ✗ ← gap!
// Impact analysis: "What breaks if we change OrderProcessingFeature?"
var impact = graph.GetImpact(typeof(OrderProcessingFeature));
// → Order, OrderLine, OrderProcessingTests, CreateOrderEndpoint, PayOrderEndpoint
// → 5 types across 3 projects need reviewWhat's Next
The dev-side DSLs provide the typed metadata. The Artifact Graph provides the cross-referencing engine. What's missing is the documentation generator — the DSL that reads all of this and produces human-readable output.
That is the Document DSL (Part III): Document<Feature<T>> → traceability matrix. Document<AggregateRoot<T>> → architecture diagram. Document<Document<T>> → its own reference docs.