Creating Local Templates¶
Purpose: Complete guide to creating custom .NET MAUI templates for team-specific patterns and conventions.
Learn local templates in: - 2 minutes: Quick Start - 10 minutes: Core Concepts - 30 minutes: Complete Reference
Quick Start (2 minutes)¶
Create a Custom Template in 4 Steps¶
# 1. Copy existing template as starting point
cp -r installer/core/templates/maui-appshell installer/local/templates/mycompany-maui
# 2. Edit manifest.json
vim installer/local/templates/mycompany-maui/manifest.json
# Update name, description:
{
"name": "mycompany-maui",
"description": "My Company's customized MAUI template with company patterns"
}
# 3. Customize code templates
# Edit files in templates/ directory:
# - Add company-specific patterns
# - Customize naming conventions
# - Add company libraries
# 4. Use your custom template
guardkit init mycompany-maui
That's it! Your custom template is ready for team use.
Learn More: See "Core Concepts" below for template structure and customization options.
Core Concepts (10 minutes)¶
Template Directory Structure¶
installer/local/templates/mycompany-maui/
├── manifest.json # Template metadata and configuration
├── settings.json # Naming conventions and layer config
├── CLAUDE.md # Claude Code guidance document
├── README.md # Human-readable template documentation
├── templates/ # Code generation templates
│ ├── domain/
│ │ ├── query-operation.cs.template
│ │ └── command-operation.cs.template
│ ├── repository/
│ │ ├── repository-interface.cs.template
│ │ └── repository-implementation.cs.template
│ ├── service/
│ │ ├── service-interface.cs.template
│ │ └── service-implementation.cs.template
│ ├── presentation/
│ │ ├── page.xaml.template
│ │ ├── page.xaml.cs.template
│ │ └── viewmodel.cs.template
│ └── testing/
│ ├── domain-test.cs.template
│ ├── repository-test.cs.template
│ └── service-test.cs.template
└── agents/ # Stack-specific AI agents (optional)
├── mycompany-domain-specialist.md
└── mycompany-test-specialist.md
Template Placeholders¶
All templates support these placeholders:
Project-Level:
- {{ProjectName}} - Root project name (e.g., "ShoppingApp")
- {{RootNamespace}} - Root namespace (e.g., "ShoppingApp")
- {{CompanyName}} - Your company name (custom)
Feature-Level:
- {{FeatureName}} - Feature/module name (e.g., "Products")
- {{Entity}} - Entity/model name (e.g., "Product")
- {{Verb}} - Action verb (e.g., "Get", "Create", "Update")
Operation-Level:
- {{OperationName}} - Complete operation name (e.g., "GetProducts")
- {{ReturnType}} - Return type (e.g., "List{{Parameters}} - Method parameters
- {{RequestType}} - Request object type
Customization Areas¶
1. Naming Conventions¶
Customize naming in settings.json:
{
"naming": {
"domain_operations": "{Verb}{Entity}",
"repositories": "I{Entity}Repository",
"services": "I{Purpose}Service",
"viewmodels": "{Feature}ViewModel",
"pages": "{Feature}Page"
},
"prohibited_suffixes": [
"UseCase",
"Engine",
"Handler",
"Processor"
]
}
2. Company-Specific Patterns¶
Add company patterns to code templates:
// templates/domain/query-operation.cs.template
namespace {{CompanyName}}.{{ProjectName}}.Domain.{{FeatureName}};
/// <summary>
/// {{OperationName}} - {{CompanyName}} standard domain operation
/// Complies with {{CompanyName}} Architecture Guidelines v2.0
/// </summary>
public class {{OperationName}}
{
// Your company's standard logging
private readonly ILogger<{{OperationName}}> _logger;
private readonly I{{Entity}}Repository _repository;
// Your company's standard constructor pattern
public {{OperationName}}(
ILogger<{{OperationName}}> logger,
I{{Entity}}Repository repository)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
// Your company's standard method pattern
public async Task<ErrorOr<{{ReturnType}}>> ExecuteAsync()
{
_logger.LogInformation("Executing {{OperationName}}");
try
{
var result = await _repository.GetAllAsync();
_logger.LogInformation(
"{{OperationName}} completed successfully. Records: {Count}",
result.IsError ? 0 : result.Value.Count
);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "{{OperationName}} failed");
return Error.Unexpected(
"{{OperationName}}.Failed",
ex.Message
);
}
}
}
3. Company Libraries¶
Include company-specific NuGet packages in README.md:
## Required Company Packages
```xml
<PackageReference Include="MyCompany.Logging" Version="2.1.0" />
<PackageReference Include="MyCompany.ErrorHandling" Version="1.5.0" />
<PackageReference Include="MyCompany.Security" Version="3.0.0" />
#### 4. Quality Gates
Customize quality gates in `manifest.json`:
```json
{
"quality_gates": {
"required": [
"All tests passing",
"MyCompany.Security scan passing",
"90%+ line coverage (company standard)",
"Code review by senior developer"
],
"recommended": [
"Performance benchmarks recorded",
"Documentation updated in Confluence"
]
}
}
Local vs Global Templates¶
Global Templates (installer/core/templates/):
- Shipped with Agentecflow
- Technology-agnostic best practices
- Updated with Agentecflow releases
- DO NOT modify (changes lost on upgrade)
Local Templates (installer/local/templates/):
- Created by your team
- Company-specific patterns
- Customized to your standards
- Preserved during Agentecflow upgrades
Override Behavior: Local templates with same name override global templates.
Learn More: See "Complete Reference" below for advanced customization and examples.
Complete Reference (30+ minutes)¶
Manifest.json Structure¶
{
"name": "mycompany-maui-appshell",
"version": "1.0.0",
"description": "MyCompany .NET MAUI template with AppShell, Domain pattern, and company standards",
"template_type": "technology-stack",
"technology": "dotnet-maui",
"company": "MyCompany",
"architecture": {
"patterns": [
"Repository Pattern (MyCompany standard)",
"Service Pattern (MyCompany standard)",
"Domain Pattern (verb-based operations)",
"ErrorOr Pattern (functional error handling)",
"MVVM Pattern",
"Dependency Injection Pattern"
],
"layers": [
{
"name": "Domain",
"description": "Business operations with MyCompany logging and security",
"patterns": ["Domain Pattern", "ErrorOr Pattern", "MyCompany Logging"],
"naming": "{Verb}{Entity}"
},
{
"name": "Repository",
"description": "Database access with MyCompany ORM patterns",
"patterns": ["Repository Pattern", "MyCompany ORM"],
"naming": "I{Entity}Repository / {Entity}Repository"
},
{
"name": "Service",
"description": "External integrations with MyCompany API standards",
"patterns": ["Service Pattern", "MyCompany API Client"],
"naming": "I{Purpose}Service / {Purpose}Service"
},
{
"name": "Presentation",
"description": "MVVM with MyCompany UI guidelines",
"patterns": ["MVVM Pattern", "MyCompany UI Framework"],
"naming": "{Feature}Page / {Feature}ViewModel"
}
],
"error_handling": {
"library": "ErrorOr + MyCompany.ErrorHandling",
"version": "2.0+ (ErrorOr), 1.5.0 (MyCompany)",
"pattern": "ErrorOr<TValue> with company error tracking"
}
},
"templates": {
"domain": [
"domain/query-operation.cs.template",
"domain/command-operation.cs.template"
],
"repository": [
"repository/repository-interface.cs.template",
"repository/repository-implementation.cs.template"
],
"service": [
"service/service-interface.cs.template",
"service/service-implementation.cs.template"
],
"presentation": [
"presentation/page.xaml.template",
"presentation/page.xaml.cs.template",
"presentation/viewmodel.cs.template"
],
"testing": [
"testing/domain-test.cs.template",
"testing/repository-test.cs.template",
"testing/service-test.cs.template"
]
},
"agents": [
"mycompany-domain-specialist.md",
"mycompany-security-specialist.md"
],
"testing": {
"framework": "xUnit + MyCompany.Testing",
"mocking": "NSubstitute",
"assertions": "FluentAssertions",
"strategy": "Outside-In TDD with company test harness",
"coverage_target": {
"line": 90,
"branch": 85
}
},
"quality_gates": {
"required": [
"All tests passing",
"90%+ line coverage (company standard)",
"85%+ branch coverage",
"MyCompany.Security scan passing",
"Code review completed"
],
"recommended": [
"Performance benchmarks documented",
"API documentation generated",
"Confluence page updated"
]
},
"prerequisites": {
"sdk": ".NET 8.0+",
"workload": "dotnet workload install maui",
"packages": [
"ErrorOr (2.0+)",
"CommunityToolkit.Mvvm",
"MyCompany.Logging (2.1.0)",
"MyCompany.ErrorHandling (1.5.0)",
"MyCompany.Security (3.0.0)"
],
"tools": [
"MyCompany CLI tools",
"MyCompany Security Scanner"
]
},
"created": "2025-10-15",
"last_updated": "2025-10-15",
"author": "MyCompany Platform Team",
"contact": "platform-team@mycompany.com"
}
Settings.json Structure¶
{
"naming": {
"domain_operations": "{Verb}{Entity}",
"repositories": "I{Entity}Repository / {Entity}Repository",
"services": "I{Purpose}Service / {Purpose}Service",
"viewmodels": "{Feature}ViewModel",
"pages": "{Feature}Page",
"tests": "{ClassName}Tests"
},
"prohibited_suffixes": [
"UseCase",
"Engine",
"Handler",
"Processor",
"Command",
"Query"
],
"layers": {
"domain": {
"path": "src/Domain",
"namespace": "{ProjectName}.Domain",
"dependencies": ["Domain.Repositories", "Domain.Services"]
},
"data": {
"path": "src/Data",
"namespace": "{ProjectName}.Data",
"dependencies": ["Domain.Repositories"]
},
"infrastructure": {
"path": "src/Infrastructure",
"namespace": "{ProjectName}.Infrastructure",
"dependencies": ["Domain.Services"]
},
"presentation": {
"path": "src/Presentation",
"namespace": "{ProjectName}.Presentation",
"dependencies": ["Domain"]
}
},
"company_standards": {
"logging": {
"library": "MyCompany.Logging",
"version": "2.1.0",
"required_in": ["Domain", "Repository", "Service"]
},
"security": {
"library": "MyCompany.Security",
"version": "3.0.0",
"required_in": ["Service", "Domain"]
},
"error_handling": {
"library": "MyCompany.ErrorHandling",
"version": "1.5.0",
"required_in": ["Domain", "Repository", "Service"]
}
}
}
Code Template Examples¶
Company-Specific Domain Operation Template¶
// templates/domain/query-operation.cs.template
using ErrorOr;
using MyCompany.Logging;
using MyCompany.ErrorHandling;
namespace {{CompanyName}}.{{ProjectName}}.Domain.{{FeatureName}};
/// <summary>
/// {{OperationName}} domain operation
/// </summary>
/// <remarks>
/// Complies with {{CompanyName}} Architecture Guidelines v2.0
/// See: https://wiki.mycompany.com/architecture/domain-operations
/// </remarks>
public class {{OperationName}}
{
private readonly ICompanyLogger<{{OperationName}}> _logger;
private readonly I{{Entity}}Repository _repository;
private readonly ICompanyErrorTracker _errorTracker;
public {{OperationName}}(
ICompanyLogger<{{OperationName}}> logger,
I{{Entity}}Repository repository,
ICompanyErrorTracker errorTracker)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_errorTracker = errorTracker ?? throw new ArgumentNullException(nameof(errorTracker));
}
/// <summary>
/// Executes the {{OperationName}} operation
/// </summary>
/// <returns>Success result with {{ReturnType}} or error details</returns>
public async Task<ErrorOr<{{ReturnType}}>> ExecuteAsync()
{
using var _ = _logger.BeginScope("{{OperationName}}");
_logger.LogInformation("Starting {{OperationName}} execution");
try
{
var result = await _repository.GetAllAsync();
if (result.IsError)
{
_logger.LogWarning(
"{{OperationName}} returned error: {ErrorCode}",
result.FirstError.Code
);
// Track error in company error tracking system
await _errorTracker.TrackAsync(
operation: "{{OperationName}}",
error: result.FirstError,
context: new { timestamp = DateTime.UtcNow }
);
return result.Errors;
}
_logger.LogInformation(
"{{OperationName}} completed successfully. Records: {Count}",
result.Value.Count
);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "{{OperationName}} failed with exception");
// Track exception in company error tracking system
await _errorTracker.TrackExceptionAsync(
operation: "{{OperationName}}",
exception: ex
);
return Error.Unexpected(
"{{Entity}}.{{OperationName}}.Failed",
$"Operation failed: {ex.Message}"
);
}
}
}
Company-Specific Repository Template¶
// templates/repository/repository-implementation.cs.template
using ErrorOr;
using Microsoft.EntityFrameworkCore;
using MyCompany.Logging;
using MyCompany.ORM;
namespace {{CompanyName}}.{{ProjectName}}.Data.Repositories;
/// <summary>
/// {{Entity}}Repository implementation using MyCompany ORM patterns
/// </summary>
public class {{Entity}}Repository : CompanyRepositoryBase<{{Entity}}>, I{{Entity}}Repository
{
private readonly ICompanyLogger<{{Entity}}Repository> _logger;
private readonly CompanyDbContext _context;
public {{Entity}}Repository(
ICompanyLogger<{{Entity}}Repository> logger,
CompanyDbContext context) : base(context)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public async Task<ErrorOr<List<{{Entity}}>>> GetAllAsync()
{
using var _ = _logger.BeginScope("GetAllAsync");
try
{
_logger.LogDebug("Fetching all {{Entity}} records");
var entities = await _context.{{Entity}}s
.AsNoTracking()
.OrderByDescending(e => e.CreatedAt)
.ToListAsync();
_logger.LogInformation("Retrieved {Count} {{Entity}} records", entities.Count);
return entities;
}
catch (DbUpdateException ex)
{
_logger.LogError(ex, "Database error retrieving {{Entity}} records");
return Error.Unexpected(
"{{Entity}}.Database.Error",
$"Database error: {ex.Message}"
);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error retrieving {{Entity}} records");
return Error.Unexpected(
"{{Entity}}.GetAll.Failed",
$"Failed to retrieve records: {ex.Message}"
);
}
}
public async Task<ErrorOr<{{Entity}}>> GetByIdAsync(Guid id)
{
using var _ = _logger.BeginScope("GetByIdAsync", new { id });
try
{
_logger.LogDebug("Fetching {{Entity}} with ID: {Id}", id);
var entity = await _context.{{Entity}}s
.AsNoTracking()
.FirstOrDefaultAsync(e => e.Id == id);
if (entity == null)
{
_logger.LogWarning("{{Entity}} not found with ID: {Id}", id);
return Error.NotFound(
"{{Entity}}.NotFound",
$"{{Entity}} with ID {id} not found"
);
}
_logger.LogInformation("Retrieved {{Entity}} with ID: {Id}", id);
return entity;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving {{Entity}} with ID: {Id}", id);
return Error.Unexpected(
"{{Entity}}.GetById.Failed",
$"Failed to retrieve {{Entity}}: {ex.Message}"
);
}
}
}
Company-Specific ViewModel Template¶
// templates/presentation/viewmodel.cs.template
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MyCompany.MAUI.ViewModels;
using MyCompany.Logging;
namespace {{CompanyName}}.{{ProjectName}}.Presentation.{{FeatureName}};
/// <summary>
/// {{ViewModelName}} - View model following MyCompany MVVM patterns
/// </summary>
public partial class {{ViewModelName}} : CompanyViewModelBase
{
private readonly ICompanyLogger<{{ViewModelName}}> _logger;
private readonly {{OperationName}} _operation;
[ObservableProperty]
private ObservableCollection<{{Entity}}> _items = new();
[ObservableProperty]
private bool _isBusy;
[ObservableProperty]
private string _errorMessage = string.Empty;
public {{ViewModelName}}(
ICompanyLogger<{{ViewModelName}}> logger,
{{OperationName}} operation)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_operation = operation ?? throw new ArgumentNullException(nameof(operation));
}
public override async Task OnAppearingAsync()
{
await base.OnAppearingAsync();
await LoadItemsAsync();
}
[RelayCommand]
private async Task LoadItemsAsync()
{
using var _ = _logger.BeginScope("LoadItemsAsync");
if (IsBusy)
{
_logger.LogDebug("Load already in progress, skipping");
return;
}
IsBusy = true;
ErrorMessage = string.Empty;
try
{
_logger.LogInformation("Loading {{Entity}} items");
var result = await _operation.ExecuteAsync();
result.Switch(
items =>
{
Items = new ObservableCollection<{{Entity}}>(items);
_logger.LogInformation("Loaded {Count} items", items.Count);
},
errors =>
{
ErrorMessage = errors.First().Description;
_logger.LogWarning("Failed to load items: {ErrorCode}", errors.First().Code);
}
);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error loading items");
ErrorMessage = "An unexpected error occurred. Please try again.";
}
finally
{
IsBusy = false;
}
}
[RelayCommand]
private async Task RefreshAsync()
{
await LoadItemsAsync();
}
}
Custom AI Agent Specifications¶
Create company-specific AI agents in agents/ directory:
<!-- agents/mycompany-domain-specialist.md -->
# MyCompany Domain Specialist Agent
## Role
Expert in MyCompany's domain operation patterns, logging standards, and error handling practices.
## Expertise
- MyCompany Architecture Guidelines v2.0
- Domain-Driven Design principles
- ErrorOr pattern with MyCompany.ErrorHandling
- MyCompany.Logging integration
- Security best practices per MyCompany standards
## Constraints
1. **ALWAYS use ICompanyLogger<T>** for logging (not ILogger<T>)
2. **ALWAYS inject ICompanyErrorTracker** for error tracking
3. **ALWAYS follow naming**: {Verb}{Entity} (no UseCase/Engine suffix)
4. **ALWAYS return ErrorOr<T>** for fallible operations
5. **ALWAYS include XML documentation** with company wiki links
## Code Generation Rules
### Domain Operation Template
```csharp
public class {Verb}{Entity}
{
private readonly ICompanyLogger<{Verb}{Entity}> _logger;
private readonly I{Entity}Repository _repository;
private readonly ICompanyErrorTracker _errorTracker;
// Constructor with null checks
// ExecuteAsync with logging scope
// Error tracking on failures
// XML documentation
}
Logging Pattern¶
using var _ = _logger.BeginScope("{OperationName}");
_logger.LogInformation("Starting {OperationName}");
// ... operation logic
_logger.LogInformation("{OperationName} completed");
Error Tracking Pattern¶
await _errorTracker.TrackAsync(
operation: "{OperationName}",
error: result.FirstError,
context: new { /* relevant context */ }
);
Quality Standards¶
- 90%+ line coverage (company standard)
- All operations logged with scopes
- All errors tracked in error tracking system
- XML documentation with wiki links
- Security review for sensitive operations
References¶
- MyCompany Architecture Guidelines
- Domain Operations Standard
- Logging Standard
- Error Handling Standard
### Testing Template with Company Standards ```csharp // templates/testing/domain-test.cs.template using FluentAssertions; using NSubstitute; using Xunit; using MyCompany.Testing; namespace {{CompanyName}}.{{ProjectName}}.Tests.Domain.{{FeatureName}}; /// <summary> /// Tests for {{OperationName}} /// </summary> /// <remarks> /// Follows MyCompany Testing Guidelines v1.5 /// See: https://wiki.mycompany.com/testing/domain-tests /// </remarks> [Collection("Domain Tests")] public class {{OperationName}}Tests : CompanyTestBase { private readonly ICompanyLogger<{{OperationName}}> _logger; private readonly I{{Entity}}Repository _repository; private readonly ICompanyErrorTracker _errorTracker; private readonly {{OperationName}} _sut; public {{OperationName}}Tests() { _logger = CreateMockLogger<{{OperationName}}>(); _repository = Substitute.For<I{{Entity}}Repository>(); _errorTracker = CreateMockErrorTracker(); _sut = new {{OperationName}}(_logger, _repository, _errorTracker); } [Fact] [Trait("Category", "Unit")] [Trait("Priority", "P0")] public async Task ExecuteAsync_WhenRepositorySucceeds_ReturnsSuccess() { // Arrange var expected = CreateTestData<List<{{Entity}}>>(); _repository.GetAllAsync().Returns(expected); // Act var result = await _sut.ExecuteAsync(); // Assert result.IsError.Should().BeFalse(); result.Value.Should().BeEquivalentTo(expected); // Verify logging (company standard) VerifyLoggingScope(_logger, "{{OperationName}}"); VerifyLogMessage(_logger, LogLevel.Information, "Starting {{OperationName}}"); VerifyLogMessage(_logger, LogLevel.Information, "completed successfully"); } [Fact] [Trait("Category", "Unit")] [Trait("Priority", "P0")] public async Task ExecuteAsync_WhenRepositoryFails_ReturnsError() { // Arrange var error = Error.Unexpected("Test.Error", "Test error"); _repository.GetAllAsync().Returns(error); // Act var result = await _sut.ExecuteAsync(); // Assert result.IsError.Should().BeTrue(); result.FirstError.Should().Be(error); // Verify error tracking (company standard) await _errorTracker.Received(1).TrackAsync( operation: "{{OperationName}}", error: Arg.Any<Error>(), context: Arg.Any<object>() ); } [Fact] [Trait("Category", "Unit")] [Trait("Priority", "P1")] public async Task ExecuteAsync_WhenExceptionThrown_TracksError() { // Arrange _repository.GetAllAsync().Returns(Task.FromException<ErrorOr<List<{{Entity}}>>>( new InvalidOperationException("Test exception") )); // Act var result = await _sut.ExecuteAsync(); // Assert result.IsError.Should().BeTrue(); result.FirstError.Type.Should().Be(ErrorType.Unexpected); // Verify exception tracking (company standard) await _errorTracker.Received(1).TrackExceptionAsync( operation: "{{OperationName}}", exception: Arg.Any<Exception>() ); } }
Distribution and Versioning¶
Version Control¶
# Add local templates to git
git add installer/local/templates/mycompany-maui/
git commit -m "feat: Add MyCompany MAUI template v1.0.0"
git tag mycompany-maui-v1.0.0
git push origin main --tags
Changelog Management¶
// manifest.json
{
"changelog": [
{
"version": "1.1.0",
"date": "2025-10-20",
"changes": [
"Added MyCompany.Security v3.1.0 integration",
"Updated logging patterns to use structured logging",
"Added performance monitoring to all operations"
]
},
{
"version": "1.0.0",
"date": "2025-10-15",
"changes": [
"Initial release",
"Company-specific Domain, Repository, Service patterns",
"Integrated MyCompany logging, error tracking, security"
]
}
]
}
Team Distribution¶
Option 1: Git Repository (Recommended)
# Team members clone and pull updates
git clone https://github.com/mycompany/agentecflow-templates.git installer/local/templates/
cd installer/local/templates/
git pull origin main
Option 2: Package Distribution
# Create tarball
cd installer/local/templates/
tar -czf mycompany-maui-v1.0.0.tar.gz mycompany-maui/
# Distribute via internal package manager or network share
# Team members extract
tar -xzf mycompany-maui-v1.0.0.tar.gz -C installer/local/templates/
Option 3: Template Registry (Enterprise)
# Publish to internal template registry
agentic template publish installer/local/templates/mycompany-maui
# Team members install
agentic template install mycompany-maui@1.0.0
Examples¶
Example 1: Add Company Logging Standard¶
Step 1: Edit domain operation template
// templates/domain/query-operation.cs.template
// Add company logging
using MyCompany.Logging;
public class {{OperationName}}
{
private readonly ICompanyLogger<{{OperationName}}> _logger;
public {{OperationName}}(ICompanyLogger<{{OperationName}}> logger, ...)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<ErrorOr<{{ReturnType}}>> ExecuteAsync()
{
using var _ = _logger.BeginScope("{{OperationName}}");
_logger.LogInformation("Executing {{OperationName}}");
// ... operation logic
_logger.LogInformation("{{OperationName}} completed");
}
}
Step 2: Update manifest.json prerequisites
Step 3: Document in CLAUDE.md
## Company Standards
### Logging
All domain operations MUST use `ICompanyLogger<T>`:
- Begin scope at method start
- Log information at operation start and completion
- Log warnings for error results
- Log errors for exceptions
Example 2: Add Company Security Patterns¶
Step 1: Create security decorator template
// templates/domain/secured-operation.cs.template
using MyCompany.Security;
[RequirePermission("{{Entity}}.Read")]
public class {{OperationName}}
{
private readonly ISecurityContext _securityContext;
private readonly I{{Entity}}Repository _repository;
public async Task<ErrorOr<{{ReturnType}}>> ExecuteAsync()
{
// Check permissions
if (!await _securityContext.HasPermissionAsync("{{Entity}}.Read"))
{
return Error.Forbidden(
"{{Entity}}.Forbidden",
"Insufficient permissions to read {{Entity}}"
);
}
// ... operation logic
}
}
Step 2: Update manifest.json
{
"templates": {
"domain": [
"domain/query-operation.cs.template",
"domain/secured-operation.cs.template"
]
},
"prerequisites": {
"packages": [
"MyCompany.Security (3.0.0)"
]
}
}
Example 3: Add Company API Client Pattern¶
Step 1: Create API service template
// templates/service/api-service-implementation.cs.template
using MyCompany.ApiClient;
public class {{ServiceName}} : CompanyApiServiceBase, I{{ServiceName}}
{
private readonly ICompanyLogger<{{ServiceName}}> _logger;
private readonly ICompanyApiClient _apiClient;
public {{ServiceName}}(
ICompanyLogger<{{ServiceName}}> logger,
ICompanyApiClient apiClient) : base(apiClient)
{
_logger = logger;
_apiClient = apiClient;
}
public async Task<ErrorOr<{{ReturnType}}>> {{MethodName}}Async({{Parameters}})
{
using var _ = _logger.BeginScope("{{MethodName}}Async");
try
{
// Use company API client with built-in auth, retry, circuit breaker
var response = await _apiClient.GetAsync<{{ReturnType}}>(
endpoint: "{{Endpoint}}",
options: new CompanyApiOptions
{
Timeout = TimeSpan.FromSeconds(30),
RetryCount = 3,
CacheEnabled = true
}
);
return response.MapToErrorOr();
}
catch (CompanyApiException ex)
{
_logger.LogError(ex, "API call failed: {{MethodName}}");
return Error.Unexpected(
"{{ServiceName}}.{{MethodName}}.Failed",
ex.Message
);
}
}
}
Step 2: Update prerequisites
FAQ¶
Q: Should I modify global templates or create local templates?¶
A: ALWAYS create local templates. Never modify global templates - your changes will be lost during Agentecflow upgrades.
# ✅ Good - Create local template
cp -r installer/core/templates/maui-appshell installer/local/templates/mycompany-maui
# ❌ Bad - Modify global template (changes lost on upgrade)
vim installer/core/templates/maui-appshell/templates/domain/query-operation.cs.template
Q: How do I share templates with my team?¶
A: Three options: 1. Git Repository (Recommended) - Version control and easy updates 2. Package Distribution - Tarball via internal package manager 3. Template Registry - Enterprise internal template registry
See "Distribution and Versioning" section above.
Q: Can I override specific files in global templates?¶
A: Yes. Local templates with matching names override global templates on a file-by-file basis:
Local: installer/local/templates/maui-appshell/templates/domain/query-operation.cs.template
Global: installer/core/templates/maui-appshell/templates/domain/query-operation.cs.template
Result: Local version used (overrides global)
Q: How do I version my templates?¶
A: Use semantic versioning in manifest.json and git tags:
{
"version": "1.2.3",
"changelog": [
{
"version": "1.2.3",
"date": "2025-10-15",
"changes": ["Bug fixes", "Updated dependencies"]
}
]
}
Q: Can I create templates for other stacks (React, Python)?¶
A: Yes! Follow the same structure:
# Create React template
cp -r installer/core/templates/react installer/local/templates/mycompany-react
# Create Python template
cp -r installer/core/templates/python installer/local/templates/mycompany-python
Update manifest.json with appropriate technology value.
Q: How do I test my templates before distributing?¶
A: Create a test project:
# Create project from your template
guardkit init mycompany-maui --output /tmp/test-project
# Verify generated files
cd /tmp/test-project
cat src/Domain/Products/GetProducts.cs
# Build and test
dotnet build
dotnet test
Q: Can I add custom placeholders?¶
A: Yes, define custom placeholders in settings.json:
{
"custom_placeholders": {
"CompanyName": "MyCompany",
"CompanyNamespace": "MyCompany.Platform",
"CompanyWikiUrl": "https://wiki.mycompany.com"
}
}
Use in templates: {{CompanyName}}, {{CompanyWikiUrl}}
Related Documentation¶
- Domain Layer Pattern - Domain operation patterns
- MAUI Template Selection - Choose base template
- Engine-to-Domain Migration - Migrate UseCase/Engine to Domain
- Agentecflow Template Specification - Template format reference
Last Updated: 2025-10-15 Version: 1.0.0 Maintained By: AI Engineer Team