Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

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

The 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)

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.
}

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:

  1. Restore. dotnet restore MyStore.Lib to ensure the generator package is current.
  2. Compile. Build MyStore.Lib with EmitCompilerGeneratedFiles=true so .g.cs files land in obj/Generated/.
  3. Diagnostics gate. Any diagnostic with severity ≥ Error halts the run; warnings are aggregated and printed at the end with file:line locations.
  4. 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

A 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.

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

The 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   │
└────────────────────────────────────────────────────────────────────┘

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

The 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

Every 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)));
    }
}

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.

⬇ Download