diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml
new file mode 100644
index 0000000..45658f9
--- /dev/null
+++ b/.github/workflows/publish-docs.yml
@@ -0,0 +1,57 @@
+name: Publish Docs
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - 'docs/**'
+ - 'src/DocSamples/**'
+ - 'mdsnippets.json'
+ - 'package.json'
+ workflow_dispatch:
+
+env:
+ DOTNET_CLI_TELEMETRY_OPTOUT: 1
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
+
+jobs:
+ publish:
+ name: Build and deploy docs to Netlify
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+
+ - name: Install .NET 10.0.x
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+
+ - name: Build DocSamples
+ run: dotnet build src/DocSamples/DocSamples.csproj -c Release
+
+ - name: Install mdsnippets
+ run: dotnet tool install -g markdownSnippets.tool
+
+ - name: Run mdsnippets
+ run: mdsnippets
+
+ - name: Install npm dependencies
+ run: npm ci
+
+ - name: Build VitePress site
+ run: npx vitepress build docs
+
+ - name: Deploy to Netlify
+ uses: nwtgck/actions-netlify@v3
+ with:
+ publish-dir: docs/.vitepress/dist
+ production-deploy: true
+ env:
+ NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
+ NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
diff --git a/.gitignore b/.gitignore
index e7c4576..8565fee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -399,3 +399,7 @@ FodyWeavers.xsd
.idea/
.DS_Store
src/CommandLineRunner/description.txt
+
+# VitePress documentation
+docs/.vitepress/cache
+docs/.vitepress/dist
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000..94aa6e0
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,3 @@
+.vitepress/cache
+.vitepress/dist
+node_modules
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
new file mode 100644
index 0000000..79dd736
--- /dev/null
+++ b/docs/.vitepress/config.ts
@@ -0,0 +1,108 @@
+import { defineConfig } from 'vitepress'
+import { withMermaid } from 'vitepress-plugin-mermaid'
+
+export default withMermaid(
+ defineConfig({
+ title: 'JasperFx',
+ description: 'The foundational .NET library behind the Critter Stack',
+ head: [
+ ['link', { rel: 'icon', href: '/jasperfx-logo.png' }]
+ ],
+
+ themeConfig: {
+ logo: '/jasperfx-logo.png',
+
+ nav: [
+ { text: 'Guide', link: '/guide/' },
+ { text: 'Code Generation', link: '/codegen/' },
+ { text: 'Command Line', link: '/cli/' },
+ { text: 'Configuration', link: '/configuration/critter-stack-defaults' },
+ {
+ text: 'Ecosystem',
+ items: [
+ { text: 'Marten', link: 'https://martendb.io' },
+ { text: 'Wolverine', link: 'https://wolverinefx.io' },
+ { text: 'Weasel', link: 'https://weasel.jasperfx.net' },
+ { text: 'GitHub', link: 'https://github.com/JasperFx/jasperfx' }
+ ]
+ }
+ ],
+
+ sidebar: [
+ {
+ text: 'Getting Started',
+ collapsed: false,
+ items: [
+ { text: 'Introduction', link: '/guide/' },
+ { text: 'Installation', link: '/guide/installation' },
+ { text: 'Quick Start', link: '/guide/quickstart' }
+ ]
+ },
+ {
+ text: 'Code Generation',
+ collapsed: false,
+ items: [
+ { text: 'Overview & Architecture', link: '/codegen/' },
+ { text: 'Frames', link: '/codegen/frames' },
+ { text: 'Variables', link: '/codegen/variables' },
+ { text: 'MethodCall', link: '/codegen/method-call' },
+ { text: 'Generated Types & Methods', link: '/codegen/generated-types' },
+ { text: 'Built-in Frames', link: '/codegen/built-in-frames' },
+ { text: 'CLI: codegen Command', link: '/codegen/cli' }
+ ]
+ },
+ {
+ text: 'Command Line',
+ collapsed: false,
+ items: [
+ { text: 'Setup & Integration', link: '/cli/' },
+ { text: 'Writing Commands', link: '/cli/writing-commands' },
+ { text: 'Arguments & Flags', link: '/cli/arguments-flags' },
+ { text: 'Environment Checks', link: '/cli/environment-checks' },
+ { text: 'Describe Command', link: '/cli/describe' }
+ ]
+ },
+ {
+ text: 'Configuration',
+ collapsed: true,
+ items: [
+ { text: 'CritterStackDefaults', link: '/configuration/critter-stack-defaults' },
+ { text: 'JasperFxOptions', link: '/configuration/jasperfx-options' }
+ ]
+ },
+ {
+ text: 'Extension Methods',
+ collapsed: true,
+ items: [
+ { text: 'Overview', link: '/extensions/' },
+ { text: 'String Extensions', link: '/extensions/string-extensions' },
+ { text: 'Enumerable Extensions', link: '/extensions/enumerable-extensions' },
+ { text: 'Reflection Extensions', link: '/extensions/reflection-extensions' }
+ ]
+ }
+ ],
+
+ socialLinks: [
+ { icon: 'github', link: 'https://github.com/JasperFx/jasperfx' }
+ ],
+
+ editLink: {
+ pattern: 'https://github.com/JasperFx/jasperfx/edit/main/docs/:path'
+ },
+
+ footer: {
+ message: 'Released under the MIT License.',
+ copyright: 'Copyright JasperFx Software'
+ },
+
+ search: {
+ provider: 'local'
+ }
+ },
+
+ mermaid: {},
+ mermaidPlugin: {
+ class: 'mermaid'
+ }
+ })
+)
diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css
new file mode 100644
index 0000000..91ee6e0
--- /dev/null
+++ b/docs/.vitepress/theme/custom.css
@@ -0,0 +1,30 @@
+:root {
+ --vp-c-brand-1: #C26A1A;
+ --vp-c-brand-2: #D4782B;
+ --vp-c-brand-3: #E08A3D;
+ --vp-c-brand-soft: rgba(194, 106, 26, 0.14);
+
+ --vp-home-hero-name-color: transparent;
+ --vp-home-hero-name-background: linear-gradient(135deg, #C26A1A 0%, #E08A3D 100%);
+ --vp-home-hero-image-background-image: linear-gradient(135deg, rgba(194, 106, 26, 0.3) 0%, rgba(224, 138, 61, 0.2) 100%);
+ --vp-home-hero-image-filter: blur(44px);
+
+ --vp-button-brand-border: transparent;
+ --vp-button-brand-text: #fff;
+ --vp-button-brand-bg: #C26A1A;
+ --vp-button-brand-hover-border: transparent;
+ --vp-button-brand-hover-text: #fff;
+ --vp-button-brand-hover-bg: #D4782B;
+ --vp-button-brand-active-border: transparent;
+ --vp-button-brand-active-text: #fff;
+ --vp-button-brand-active-bg: #A85A16;
+}
+
+.dark {
+ --vp-c-brand-1: #D4782B;
+ --vp-c-brand-2: #E08A3D;
+ --vp-c-brand-3: #ECA050;
+ --vp-c-brand-soft: rgba(212, 120, 43, 0.16);
+
+ --vp-home-hero-image-background-image: linear-gradient(135deg, rgba(194, 106, 26, 0.2) 0%, rgba(224, 138, 61, 0.15) 100%);
+}
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
new file mode 100644
index 0000000..42fe9a9
--- /dev/null
+++ b/docs/.vitepress/theme/index.ts
@@ -0,0 +1,4 @@
+import DefaultTheme from 'vitepress/theme'
+import './custom.css'
+
+export default DefaultTheme
diff --git a/docs/cli/arguments-flags.md b/docs/cli/arguments-flags.md
new file mode 100644
index 0000000..1e09a06
--- /dev/null
+++ b/docs/cli/arguments-flags.md
@@ -0,0 +1,100 @@
+# Arguments and Flags
+
+Command inputs are plain C# classes whose properties are mapped to arguments and flags.
+
+## Defining an Input Class
+
+
+
+```cs
+public class BuildInput
+{
+ [Description("The target configuration")]
+ public string Configuration { get; set; } = "Debug";
+
+ [Description("The output directory")]
+ public string OutputPath { get; set; } = "./bin";
+
+ [FlagAlias("verbose", 'v')]
+ [Description("Enable verbose output")]
+ public bool VerboseFlag { get; set; }
+
+ [FlagAlias("force", 'f')]
+ [Description("Force a clean rebuild")]
+ public bool ForceFlag { get; set; }
+
+ [Description("Maximum degree of parallelism")]
+ public int ParallelCount { get; set; } = 4;
+}
+```
+snippet source | anchor
+
+
+```cs
+public class BuildInput
+{
+ [Description("The target configuration")]
+ public string Configuration { get; set; } = "Debug";
+
+ [Description("The output directory")]
+ public string OutputPath { get; set; } = "./bin";
+
+ [FlagAlias("verbose", 'v')]
+ [Description("Enable verbose output")]
+ public bool VerboseFlag { get; set; }
+
+ [FlagAlias("force", 'f')]
+ [Description("Force a clean rebuild")]
+ public bool ForceFlag { get; set; }
+
+ [Description("Maximum degree of parallelism")]
+ public int ParallelCount { get; set; } = 4;
+}
+```
+snippet source | anchor
+
+
+## Arguments
+
+Public properties that are not suffixed with `Flag` are treated as positional arguments. They are matched in the order they appear on the class.
+
+- **Required arguments** -- Non-nullable properties without a default value
+- **Optional arguments** -- Properties with a default value
+
+## Flags
+
+Properties whose names end with `Flag` are treated as command line flags (options).
+
+### Flag Aliases
+
+Use `[FlagAlias]` to define short and long flag names:
+
+```csharp
+[FlagAlias("verbose", 'v')]
+public bool VerboseFlag { get; set; }
+```
+
+This allows both `--verbose` and `-v` on the command line.
+
+### Flag Types
+
+| .NET Type | CLI Syntax | Example |
+|-----------|-----------|---------|
+| `bool` | `--flag` | `--verbose` |
+| `string` | `--flag value` | `--output ./bin` |
+| `int` | `--flag value` | `--parallel 8` |
+| `Enum` | `--flag value` | `--level Debug` |
+
+## Description Attribute
+
+Use `[Description]` on properties to provide help text displayed in the CLI:
+
+```csharp
+[Description("The target configuration")]
+public string Configuration { get; set; } = "Debug";
+```
+
+## Next Steps
+
+- [Writing Commands](/cli/writing-commands) -- Build commands using input classes
+- [Environment Checks](/cli/environment-checks) -- Startup validation
diff --git a/docs/cli/describe.md b/docs/cli/describe.md
new file mode 100644
index 0000000..9231aca
--- /dev/null
+++ b/docs/cli/describe.md
@@ -0,0 +1,91 @@
+# Describe Command
+
+The `describe` command outputs a summary of your application's configuration and registered components. It is built in and available automatically.
+
+## Running Describe
+
+```bash
+dotnet run -- describe
+```
+
+This prints information about all registered `ISystemPart` instances, including those added by Critter Stack libraries like Marten and Wolverine.
+
+## Custom System Parts
+
+Implement `ISystemPart` to add your own sections to the describe output:
+
+
+
+```cs
+public class MessagingSystemPart : SystemPartBase
+{
+ public MessagingSystemPart()
+ : base("Messaging Subsystem", new Uri("system://messaging"))
+ {
+ }
+
+ public override Task WriteToConsole()
+ {
+ AnsiConsole.MarkupLine("[bold]Transport:[/] RabbitMQ");
+ AnsiConsole.MarkupLine("[bold]Queues:[/] 12 active");
+ AnsiConsole.MarkupLine("[bold]Consumers:[/] 8 running");
+ return Task.CompletedTask;
+ }
+}
+```
+snippet source | anchor
+
+
+```cs
+public class MessagingSystemPart : SystemPartBase
+{
+ public MessagingSystemPart()
+ : base("Messaging Subsystem", new Uri("system://messaging"))
+ {
+ }
+
+ public override Task WriteToConsole()
+ {
+ AnsiConsole.MarkupLine("[bold]Transport:[/] RabbitMQ");
+ AnsiConsole.MarkupLine("[bold]Queues:[/] 12 active");
+ AnsiConsole.MarkupLine("[bold]Consumers:[/] 8 running");
+ return Task.CompletedTask;
+ }
+}
+```
+snippet source | anchor
+
+
+Register your system part through `JasperFxOptions`:
+
+```csharp
+services.AddJasperFx(opts =>
+{
+ opts.Services.AddSingleton();
+});
+```
+
+## IDescriptionWriter
+
+The `IDescriptionWriter` interface provides methods for structured output:
+
+| Method | Purpose |
+|--------|---------|
+| `BulletItem(string)` | Write a bullet point |
+| `Header(string)` | Write a section header |
+| `Write(string)` | Write plain text |
+
+## Built-in System Parts
+
+JasperFx itself registers a system part for `JasperFxOptions` that reports:
+
+- Active profile (Development, Staging, Production)
+- Required files
+- Registered environment checks
+
+Other Critter Stack libraries add their own parts automatically when referenced.
+
+## Next Steps
+
+- [Environment Checks](/cli/environment-checks) -- Validate dependencies at startup
+- [JasperFx Options](/configuration/jasperfx-options) -- Full options reference
diff --git a/docs/cli/environment-checks.md b/docs/cli/environment-checks.md
new file mode 100644
index 0000000..3f159a3
--- /dev/null
+++ b/docs/cli/environment-checks.md
@@ -0,0 +1,180 @@
+# Environment Checks
+
+Environment checks let you verify that external dependencies are available when your application starts.
+
+## Registering Checks
+
+Use the `CheckEnvironment` extension methods on `IServiceCollection`:
+
+
+
+```cs
+public static void RegisterChecks(IServiceCollection services)
+{
+ // Async check with IServiceProvider access
+ services.CheckEnvironment(
+ "Database is reachable",
+ async (IServiceProvider sp, CancellationToken ct) =>
+ {
+ // Throw an exception to indicate failure
+ await Task.CompletedTask;
+ });
+
+ // Synchronous check
+ services.CheckEnvironment(
+ "Configuration file exists",
+ (IServiceProvider sp) =>
+ {
+ if (!File.Exists("appsettings.json"))
+ {
+ throw new FileNotFoundException("Missing configuration file");
+ }
+ });
+}
+```
+snippet source | anchor
+
+
+```cs
+public static void RegisterChecks(IServiceCollection services)
+{
+ // Async check with IServiceProvider access
+ services.CheckEnvironment(
+ "Database is reachable",
+ async (IServiceProvider sp, CancellationToken ct) =>
+ {
+ // Throw an exception to indicate failure
+ await Task.CompletedTask;
+ });
+
+ // Synchronous check
+ services.CheckEnvironment(
+ "Configuration file exists",
+ (IServiceProvider sp) =>
+ {
+ if (!File.Exists("appsettings.json"))
+ {
+ throw new FileNotFoundException("Missing configuration file");
+ }
+ });
+}
+```
+snippet source | anchor
+
+
+## Typed Service Checks
+
+You can resolve a registered service and check it directly:
+
+
+
+```cs
+public static void RegisterTypedCheck(IServiceCollection services)
+{
+ services.CheckEnvironment(
+ "Required config keys present",
+ config =>
+ {
+ if (config?.GetValue("ConnectionString") is null)
+ {
+ throw new Exception("ConnectionString is required");
+ }
+ });
+}
+```
+snippet source | anchor
+
+
+```cs
+public static void RegisterTypedCheck(IServiceCollection services)
+{
+ services.CheckEnvironment(
+ "Required config keys present",
+ config =>
+ {
+ if (config?.GetValue("ConnectionString") is null)
+ {
+ throw new Exception("ConnectionString is required");
+ }
+ });
+}
+```
+snippet source | anchor
+
+
+## Running Checks
+
+Run all registered checks from the command line:
+
+```bash
+dotnet run -- check-env
+```
+
+Each check runs and reports success or failure. The command exits with a non-zero code if any check fails.
+
+## Inline Registration via JasperFxOptions
+
+You can also register checks directly through `JasperFxOptions`:
+
+
+
+```cs
+public static void ConfigureOptions(IServiceCollection services)
+{
+ services.AddJasperFx(opts =>
+ {
+ // Register an environment check inline
+ opts.RegisterEnvironmentCheck(
+ "Database connectivity",
+ async (sp, ct) =>
+ {
+ // Verify your database is accessible
+ await Task.CompletedTask;
+ });
+ });
+}
+```
+snippet source | anchor
+
+
+```cs
+public static void ConfigureOptions(IServiceCollection services)
+{
+ services.AddJasperFx(opts =>
+ {
+ // Register an environment check inline
+ opts.RegisterEnvironmentCheck(
+ "Database connectivity",
+ async (sp, ct) =>
+ {
+ // Verify your database is accessible
+ await Task.CompletedTask;
+ });
+ });
+}
+```
+snippet source | anchor
+
+
+## Required Files
+
+A common check is verifying that a configuration file exists. JasperFx provides a shorthand:
+
+```csharp
+services.AddJasperFx(opts =>
+{
+ opts.RequireFile("appsettings.json");
+ opts.RequireFile("certs/server.pfx");
+});
+```
+
+## Best Practices
+
+- Keep checks fast. They run at startup and slow checks delay your application.
+- Throw descriptive exceptions so failures are easy to diagnose.
+- Use environment checks for external dependencies (databases, files, services) rather than internal validation.
+
+## Next Steps
+
+- [Describe](/cli/describe) -- Customize application description output
+- [Configuration](/configuration/jasperfx-options) -- Full options reference
diff --git a/docs/cli/index.md b/docs/cli/index.md
new file mode 100644
index 0000000..23249b8
--- /dev/null
+++ b/docs/cli/index.md
@@ -0,0 +1,82 @@
+# Command Line Tooling
+
+JasperFx includes a lightweight CLI framework for building commands that integrate with `Microsoft.Extensions.Hosting`.
+
+## Enabling the CLI
+
+Wire up the JasperFx command line by calling `ApplyJasperFxExtensions` on your host builder:
+
+
+
+```cs
+await Host
+ .CreateDefaultBuilder()
+ .ApplyJasperFxExtensions()
+ .RunJasperFxCommands(args);
+```
+snippet source | anchor
+
+
+```cs
+await Host
+ .CreateDefaultBuilder()
+ .ApplyJasperFxExtensions()
+ .RunJasperFxCommands(args);
+```
+snippet source | anchor
+
+
+Alternatively, use `RunJasperFxCommands` for more control over host configuration:
+
+
+
+```cs
+var builder = Host.CreateDefaultBuilder();
+
+builder.ConfigureServices(services =>
+{
+ // Register your services here
+});
+
+await builder
+ .ApplyJasperFxExtensions()
+ .RunJasperFxCommands(args);
+```
+snippet source | anchor
+
+
+```cs
+var builder = Host.CreateDefaultBuilder();
+
+builder.ConfigureServices(services =>
+{
+ // Register your services here
+});
+
+await builder
+ .ApplyJasperFxExtensions()
+ .RunJasperFxCommands(args);
+```
+snippet source | anchor
+
+
+## Built-in Commands
+
+JasperFx ships with several commands out of the box:
+
+| Command | Description |
+|---------|-------------|
+| `help` | List all available commands |
+| `describe` | Describe the application configuration |
+| `check-env` | Run all registered environment checks |
+
+## Command Discovery
+
+Commands are discovered automatically from referenced assemblies that carry the `[JasperFxTool]` attribute. Your own commands are found through assembly scanning.
+
+## Topics
+
+- [Writing Commands](/cli/writing-commands) -- Create synchronous and async commands
+- [Arguments and Flags](/cli/arguments-flags) -- Define inputs with attributes
+- [Environment Checks](/cli/environment-checks) -- Validate runtime dependencies
+- [Describe](/cli/describe) -- Customize the describe output
diff --git a/docs/cli/writing-commands.md b/docs/cli/writing-commands.md
new file mode 100644
index 0000000..7fcb6ac
--- /dev/null
+++ b/docs/cli/writing-commands.md
@@ -0,0 +1,128 @@
+# Writing Commands
+
+JasperFx provides two base classes for building CLI commands.
+
+## Synchronous Commands
+
+Extend `JasperFxCommand` for commands that do not need async operations:
+
+
+
+```cs
+[Description("Say hello to someone")]
+public class GreetingCommand : JasperFxCommand
+{
+ public override bool Execute(GreetingInput input)
+ {
+ Console.WriteLine($"Hello, {input.Name}!");
+ return true;
+ }
+}
+```
+snippet source | anchor
+
+
+```cs
+[Description("Say hello to someone")]
+public class GreetingCommand : JasperFxCommand
+{
+ public override bool Execute(GreetingInput input)
+ {
+ Console.WriteLine($"Hello, {input.Name}!");
+ return true;
+ }
+}
+```
+snippet source | anchor
+
+
+The `Execute` method returns `true` for success or `false` for failure. The exit code is set accordingly.
+
+## Async Commands
+
+Extend `JasperFxAsyncCommand` when you need to perform async work:
+
+
+
+```cs
+[Description("Say hello to someone asynchronously")]
+public class AsyncGreetingCommand : JasperFxAsyncCommand
+{
+ public override async Task Execute(GreetingInput input)
+ {
+ await Task.Delay(100);
+ Console.WriteLine($"Hello, {input.Name}!");
+ return true;
+ }
+}
+```
+snippet source | anchor
+
+
+```cs
+[Description("Say hello to someone asynchronously")]
+public class AsyncGreetingCommand : JasperFxAsyncCommand
+{
+ public override async Task Execute(GreetingInput input)
+ {
+ await Task.Delay(100);
+ Console.WriteLine($"Hello, {input.Name}!");
+ return true;
+ }
+}
+```
+snippet source | anchor
+
+
+## Input Classes
+
+Every command takes an input class that defines its arguments and flags:
+
+
+
+```cs
+public class GreetingInput
+{
+ [Description("The name to greet")]
+ public string Name { get; set; } = "World";
+}
+```
+snippet source | anchor
+
+
+```cs
+public class GreetingInput
+{
+ [Description("The name to greet")]
+ public string Name { get; set; } = "World";
+}
+```
+snippet source | anchor
+
+
+Properties on the input class are automatically mapped to command line arguments. See [Arguments and Flags](/cli/arguments-flags) for the full attribute reference.
+
+## Command Naming
+
+By default, the command name is derived from the class name by removing the `Command` suffix and converting to kebab-case. For example, `GreetingCommand` becomes `greeting`.
+
+## Usage Patterns
+
+Commands can define multiple usage patterns similar to Git:
+
+```csharp
+public class MyCommand : JasperFxCommand
+{
+ public MyCommand()
+ {
+ Usage("Default usage").Arguments(x => x.Name);
+ }
+
+ public override bool Execute(MyInput input) => true;
+}
+```
+
+## Next Steps
+
+- [Arguments and Flags](/cli/arguments-flags) -- Detailed attribute reference
+- [Environment Checks](/cli/environment-checks) -- Register startup validations
diff --git a/docs/codegen/built-in-frames.md b/docs/codegen/built-in-frames.md
new file mode 100644
index 0000000..07584f9
--- /dev/null
+++ b/docs/codegen/built-in-frames.md
@@ -0,0 +1,242 @@
+# Built-in Frames
+
+JasperFx ships with several ready-to-use `Frame` implementations. These cover the most common code generation patterns so you rarely need to write custom frames for straightforward scenarios.
+
+## CommentFrame
+
+Writes a single-line C# comment into the generated code. Useful for making generated source more readable.
+
+
+
+```cs
+public static void UsingCommentFrame()
+{
+ var rules = new GenerationRules("MyApp.Generated");
+ var assembly = new GeneratedAssembly(rules);
+
+ var type = assembly.AddType("Worker", typeof(IWorker));
+ var method = type.MethodFor(nameof(IWorker.Execute));
+
+ // CommentFrame writes a C# comment line
+ method.Frames.Add(new CommentFrame("Begin processing"));
+ method.Frames.Code("Console.WriteLine(\"Working...\");");
+}
+
+public interface IWorker
+{
+ void Execute();
+}
+```
+snippet source | anchor
+
+
+Generated output:
+
+```
+// Begin processing
+```
+
+## CodeFrame
+
+A general-purpose frame that writes a single statement from a format string. Variable placeholders are resolved automatically.
+
+
+
+```cs
+public static void UsingCodeFrame()
+{
+ var rules = new GenerationRules("MyApp.Generated");
+ var assembly = new GeneratedAssembly(rules);
+
+ var type = assembly.AddType("Processor", typeof(IProcessor));
+ var method = type.MethodFor(nameof(IProcessor.Process));
+
+ // CodeFrame uses a format string with variable placeholders
+ method.Frames.Code("Console.WriteLine({0});", Use.Type());
+}
+
+public interface IProcessor
+{
+ void Process(string input);
+}
+```
+snippet source | anchor
+
+
+The `{0}` placeholder is replaced by the resolved variable's `Usage` name. You can reference multiple variables with `{1}`, `{2}`, etc.
+
+## ReturnFrame
+
+Generates a `return` statement, optionally returning a resolved variable.
+
+
+
+```cs
+public static void UsingReturnFrame()
+{
+ var rules = new GenerationRules("MyApp.Generated");
+ var assembly = new GeneratedAssembly(rules);
+
+ var type = assembly.AddType("Checker", typeof(IChecker));
+ var method = type.MethodFor(nameof(IChecker.IsValid));
+
+ method.Frames.Code("var result = {0} != null;", Use.Type