CLI Tooling
The CMF ships a dotnet tool for scaffolding and orchestration:
$ cmf new MyStore
Created MyStore.sln with 7 projects (Lib, Abstractions, Infrastructure.Postgres,
Server, Client, Shared, Lib.Testing)
$ cmf add bounded-context Catalog
$ cmf add aggregate Product --context Catalog
Created Product.cs + Commands + Events
$ cmf add admin-module Products --aggregate Product
$ cmf add widget ProductList --aggregate Product --display List
$ cmf generate
Building MyStore.Lib... Generated 34 files. Build succeeded.
$ cmf migrate
Generated migration: 20260319_InitialCatalog$ cmf new MyStore
Created MyStore.sln with 7 projects (Lib, Abstractions, Infrastructure.Postgres,
Server, Client, Shared, Lib.Testing)
$ cmf add bounded-context Catalog
$ cmf add aggregate Product --context Catalog
Created Product.cs + Commands + Events
$ cmf add admin-module Products --aggregate Product
$ cmf add widget ProductList --aggregate Product --display List
$ cmf generate
Building MyStore.Lib... Generated 34 files. Build succeeded.
$ cmf migrate
Generated migration: 20260319_InitialCatalogThe CLI is a thin shell over the same Cmf.Lib library that the Roslyn generators use, so anything the CLI can do is also available programmatically. Internally cmf is a dotnet tool packaged as Cmf.Cli and installed with dotnet tool install --global Cmf.Cli or pinned in .config/dotnet-tools.json for repo-local use.
Command Map
| Command | Purpose | Stage(s) touched |
|---|---|---|
cmf new <Name> |
Scaffold a fresh solution from the standard 7-project template | — |
cmf add bounded-context <Name> |
Add a new context folder with empty Aggregates/, Commands/, Queries/, Events/ |
— |
cmf add aggregate <Name> --context <BC> |
Stub a partial class with [AggregateRoot], an [EntityId], and one example [Property] |
— |
cmf add admin-module <Name> --aggregate <T> |
Stub an [AdminModule] declaration |
— |
cmf add widget <Name> --aggregate <T> --display <Type> |
Stub a [PageWidget] declaration |
— |
cmf add workflow <Name> --for <Aggregate> |
Stub a [Workflow] with placeholder stages and gates |
— |
cmf add feature <Id> --title "<title>" |
Append a [Feature] to the requirements file with empty acceptance criteria |
— |
cmf generate |
Force a Roslyn build of MyStore.Lib so generated files are written to disk for inspection |
0–3 |
cmf generate --reports |
Same as above, plus emit Stage 4–5 reports | 0–5 |
cmf validate |
Run all CMF analyzers (CMF1xx–CMF4xx) without producing binaries | 1 only |
cmf migrate |
Invoke dotnet ef migrations add against the generated DbContext with an auto-derived migration name |
2 |
cmf migrate --apply |
Same as above plus dotnet ef database update against the configured connection string |
2 |
cmf report <kind> |
Emit a specific report (requirements, api, errors, coverage, traceability, mermaid) |
5 |
cmf design |
Launch the interactive Spectre.Console TUI (see below) | — |
cmf doctor |
Diagnose toolchain issues (.NET version, EF tools, missing analyzers, dirty generated files) | — |
Scaffolding: How cmf add aggregate Works
The scaffolding commands are deliberately minimal — they emit a stub, not a working aggregate. Their job is to spare developers from typing the boilerplate of namespace, partial class, and one attribute, and to make sure the file ends up in the conventional folder. Anything beyond that, the developer writes by hand.
$ cmf add aggregate Invoice --context Billing
✓ created src/MyStore.Lib/Billing/Aggregates/Invoice.cs
✓ created src/MyStore.Lib/Billing/Commands/CreateInvoiceCommand.cs (empty stub)
✓ created src/MyStore.Lib/Billing/Events/InvoiceCreatedEvent.cs (empty stub)$ cmf add aggregate Invoice --context Billing
✓ created src/MyStore.Lib/Billing/Aggregates/Invoice.cs
✓ created src/MyStore.Lib/Billing/Commands/CreateInvoiceCommand.cs (empty stub)
✓ created src/MyStore.Lib/Billing/Events/InvoiceCreatedEvent.cs (empty stub)The generated stub for Invoice.cs:
namespace MyStore.Lib.Billing.Aggregates;
[AggregateRoot("Invoice", BoundedContext = "Billing")]
public partial class Invoice
{
[EntityId] public partial InvoiceId Id { get; }
// Add [Property], [Composition], [Association] declarations here.
// Add [Invariant] methods to enforce business rules.
}namespace MyStore.Lib.Billing.Aggregates;
[AggregateRoot("Invoice", BoundedContext = "Billing")]
public partial class Invoice
{
[EntityId] public partial InvoiceId Id { get; }
// Add [Property], [Composition], [Association] declarations here.
// Add [Invariant] methods to enforce business rules.
}Two things matter about this stub. First, it compiles immediately — the generator will see one [EntityId] and emit a complete (if useless) Stage 2 graph. Second, the file uses the partial keyword so there is no friction when the developer wants to add hand-written behavior alongside generated code.
cmf generate: Orchestration and Errors
cmf generate is the gateway to the Roslyn pipeline. It performs four steps:
- Restore.
dotnet restore MyStore.Libto ensure the generator package is current. - Compile. Build
MyStore.LibwithEmitCompilerGeneratedFiles=trueso.g.csfiles land inobj/Generated/. - Diagnostics gate. Any diagnostic with severity ≥
Errorhalts the run; warnings are aggregated and printed at the end with file:line locations. - Summary. Print a count of files generated per stage and the total elapsed time.
A successful run looks like this:
$ cmf generate
Restoring MyStore.Lib ✓ 0.4s
Compiling MyStore.Lib (Roslyn 4.13) ✓ 3.1s
Stage 0 Discovery 17 aggregates, 8 widgets, 4 workflows, 32 requirements
Stage 1 Validation 0 errors, 2 warnings
Stage 2 Domain 187 files
Stage 3 UI/API 124 files
Stage 4 Cross-cutting 16 files
Total 327 files in 4.8s
Warnings:
CMF210 src/MyStore.Lib/Catalog/Aggregates/Product.cs(42,5):
[Composition] property "Variants" has no min-cardinality; defaulting to 0..*
CMF430 src/MyStore.Lib/Requirements/Features.cs(118,5):
Feature FEATURE-203 has no [Implements] in production code$ cmf generate
Restoring MyStore.Lib ✓ 0.4s
Compiling MyStore.Lib (Roslyn 4.13) ✓ 3.1s
Stage 0 Discovery 17 aggregates, 8 widgets, 4 workflows, 32 requirements
Stage 1 Validation 0 errors, 2 warnings
Stage 2 Domain 187 files
Stage 3 UI/API 124 files
Stage 4 Cross-cutting 16 files
Total 327 files in 4.8s
Warnings:
CMF210 src/MyStore.Lib/Catalog/Aggregates/Product.cs(42,5):
[Composition] property "Variants" has no min-cardinality; defaulting to 0..*
CMF430 src/MyStore.Lib/Requirements/Features.cs(118,5):
Feature FEATURE-203 has no [Implements] in production codeA failing run is more interesting. The generator does not attempt to recover — when Stage 1 reports a hard error, Stages 2–4 are skipped entirely, so no half-generated .g.cs files exist on disk:
$ cmf generate
Stage 1 Validation 2 errors
CMF105 src/MyStore.Lib/Catalog/Aggregates/Product.cs(28,5):
[AggregateRoot] requires exactly one [EntityId]; found 0
CMF210 src/MyStore.Lib/Catalog/Aggregates/Product.cs(33,5):
[Composition] of "ProductVariant" creates a cycle through Product → ProductVariant → Product
Generation aborted. Stages 2–4 were not run.
Run `cmf doctor` for help interpreting these errors.$ cmf generate
Stage 1 Validation 2 errors
CMF105 src/MyStore.Lib/Catalog/Aggregates/Product.cs(28,5):
[AggregateRoot] requires exactly one [EntityId]; found 0
CMF210 src/MyStore.Lib/Catalog/Aggregates/Product.cs(33,5):
[Composition] of "ProductVariant" creates a cycle through Product → ProductVariant → Product
Generation aborted. Stages 2–4 were not run.
Run `cmf doctor` for help interpreting these errors.This is intentional: an aborted Stage 1 leaves the previous build's .g.cs files in place, so the project keeps compiling with the last-known-good output while the developer fixes the new errors.
cmf validate: Faster Than generate
cmf validate runs only Stage 0 (discovery) and Stage 1 (validation). It does not invoke the C# compiler, does not write any files, and exits with a non-zero status code on any analyzer error. CI pipelines run it first as a fail-fast gate before the much slower dotnet build:
- name: Validate CMF declarations
run: cmf validate --no-restore
# ~600ms even on a 200-aggregate solution- name: Validate CMF declarations
run: cmf validate --no-restore
# ~600ms even on a 200-aggregate solutionThe full analyzer family is documented in Part 17: Extending the M3, but the headline categories are:
| Range | Owner | What it checks |
|---|---|---|
CMF1xx |
DDD | Aggregate shape, IDs, invariants, command/event consistency |
CMF2xx |
Composition | Cycle detection, ownership rules, cardinality |
CMF3xx |
Shared kernel | Forbidden namespaces, server-only types in WASM-targeted assemblies |
CMF4xx |
Requirements | Coverage thresholds, missing [Implements] / [TestFor], lifecycle violations |
CMF5xx |
Workflow | Unreachable stages, missing transition guards, role/policy gaps |
CMF6xx |
Pages/Admin | Widget/aggregate mismatches, AdminAction with no command target |
Each rule has a documentation URL embedded in the diagnostic so editors can Ctrl+. to a fix-it page.
cmf design: The Interactive TUI
cmf design opens a Spectre.Console terminal UI that visualizes the M1 model the generator has discovered. It is intended for two scenarios: onboarding a developer to an unfamiliar bounded context, and quickly answering "what touches this aggregate?" before refactoring.
┌─ MyStore — Catalog ────────────────────────────────────────────────┐
│ Aggregates Selected: Product │
│ ────────── │
│ ▸ Product (5 props) Properties │
│ Category (3 props) Name string required│
│ ProductVariant (4 props) Sku string required│
│ Tag (2 props) Price Money │
│ Details StreamField │
│ Workflows Variants Composition[] │
│ ───────── CategoryId Association │
│ Editorial (5 stages) │
│ Implements │
│ Admin FEATURE-101 ✓ │
│ ───── FEATURE-102 ✓ │
│ Products FEATURE-204 ⚠ no test │
│ │
│ Widgets Generated (Stage 2+3) │
│ ─────── 21 files, ≈1,445 lines │
│ ProductList │
│ ProductShow │
│ │
│ [↑/↓] navigate [Enter] open in editor [g] regenerate [q] quit │
└────────────────────────────────────────────────────────────────────┘┌─ MyStore — Catalog ────────────────────────────────────────────────┐
│ Aggregates Selected: Product │
│ ────────── │
│ ▸ Product (5 props) Properties │
│ Category (3 props) Name string required│
│ ProductVariant (4 props) Sku string required│
│ Tag (2 props) Price Money │
│ Details StreamField │
│ Workflows Variants Composition[] │
│ ───────── CategoryId Association │
│ Editorial (5 stages) │
│ Implements │
│ Admin FEATURE-101 ✓ │
│ ───── FEATURE-102 ✓ │
│ Products FEATURE-204 ⚠ no test │
│ │
│ Widgets Generated (Stage 2+3) │
│ ─────── 21 files, ≈1,445 lines │
│ ProductList │
│ ProductShow │
│ │
│ [↑/↓] navigate [Enter] open in editor [g] regenerate [q] quit │
└────────────────────────────────────────────────────────────────────┘Pressing Enter on a property opens the source file at the right line in the configured editor ($EDITOR, falling back to code -g). Pressing g triggers cmf generate in the background and refreshes the panel when it completes. The TUI is read-only by design — it never edits files — so concurrent edits in an IDE are safe.
cmf report: Build-Time Reports
Reports are Stage 5 outputs and live in artifacts/reports/. They are intentionally markdown-first so PR reviewers can read them inline:
$ cmf report requirements
✓ artifacts/reports/requirements.md
✓ artifacts/reports/requirements.json
✓ artifacts/reports/requirements.csv
$ cmf report api
✓ artifacts/reports/api-inventory.md
✓ artifacts/reports/openapi.json
$ cmf report mermaid
✓ artifacts/reports/aggregates.mmd
✓ artifacts/reports/page-tree.mmd
✓ artifacts/reports/workflow-fulfillment.mmd$ cmf report requirements
✓ artifacts/reports/requirements.md
✓ artifacts/reports/requirements.json
✓ artifacts/reports/requirements.csv
$ cmf report api
✓ artifacts/reports/api-inventory.md
✓ artifacts/reports/openapi.json
$ cmf report mermaid
✓ artifacts/reports/aggregates.mmd
✓ artifacts/reports/page-tree.mmd
✓ artifacts/reports/workflow-fulfillment.mmdThe mermaid outputs are particularly cheap to produce because the M1 model already contains every relationship — the reporter just has to walk the graph and emit nodes and edges. Combined with the mermaid rendering pipeline, the diagrams can be embedded directly in the project's README.md and updated automatically by CI.
cmf doctor
cmf doctor runs a checklist that catches the common environment problems:
$ cmf doctor
✓ .NET SDK 10.0.100
✓ Roslyn 4.13.0 (matches Cmf.Generators expectation)
✓ EF Core tools 10.0.0 installed globally
✓ Cmf.Lib package version 1.4.2 (latest 1.4.2)
✗ obj/Generated/ contains files modified after the last build
→ Hand edits to generated files are silently overwritten.
→ Run `cmf clean --generated` to discard them.
✓ No CMF analyzer warnings ignored in .editorconfig
✓ MyStore.Shared has no forbidden package references$ cmf doctor
✓ .NET SDK 10.0.100
✓ Roslyn 4.13.0 (matches Cmf.Generators expectation)
✓ EF Core tools 10.0.0 installed globally
✓ Cmf.Lib package version 1.4.2 (latest 1.4.2)
✗ obj/Generated/ contains files modified after the last build
→ Hand edits to generated files are silently overwritten.
→ Run `cmf clean --generated` to discard them.
✓ No CMF analyzer warnings ignored in .editorconfig
✓ MyStore.Shared has no forbidden package referencesEvery failed check has a remediation hint. cmf doctor --fix will apply the safe ones (e.g. cleaning generated files) automatically.
Extension: Custom Generators
Teams that need a domain-specific DSL on top of the standard six can ship their own generator and register it via Cmf.Generators.Hosting:
[CmfGenerator("Billing.InvoiceTemplates")]
public sealed class InvoiceTemplateGenerator : ICmfGenerator
{
public void Configure(CmfGeneratorContext ctx)
{
ctx.OnAggregateDiscovered(agg => agg.HasPart("Invoiceable"),
agg => ctx.EmitFile(
$"{agg.Name}InvoiceTemplate.g.cs",
Templates.Render(agg)));
}
}[CmfGenerator("Billing.InvoiceTemplates")]
public sealed class InvoiceTemplateGenerator : ICmfGenerator
{
public void Configure(CmfGeneratorContext ctx)
{
ctx.OnAggregateDiscovered(agg => agg.HasPart("Invoiceable"),
agg => ctx.EmitFile(
$"{agg.Name}InvoiceTemplate.g.cs",
Templates.Render(agg)));
}
}Once the package is referenced, cmf doctor will list the new generator under "third-party generators", cmf validate will run its analyzers, and cmf design will show its outputs in the artifact panel. Extension is covered in detail in Part 17: Extending the M3 with a New DSL.