Imported from IntentArchitect/Intent.Modules (
Modules/.opencode/skills/file-builder-expert/SKILL.md). Install upstream withnpx skills add IntentArchitect/Intent.Modules --skill file-builder-expert. Copyright stays with the author.
File Builder Expert
[!TIP] Read more if you want to know about builder APIs, patterns, or troubleshooting:
- API Cheatsheet | Patterns | Troubleshooting (To conserve tokens, avoid reading these for minor updates.)
Musts
- Inherit from
CSharpTemplateBase<TModel>, implementICSharpFileBuilderTemplate, and exposeCSharpFile. - Initialize
CSharpFilestructure in constructor. - Implement config/transform methods:
DefineFileConfig() => CSharpFile.GetConfig();andTransformText() => CSharpFile.ToString();. - Use flow builders (
AddIfStatement,AddForEachStatement) andCSharpInvocationStatement. - Register
OnBuild/AfterBuildcallbacks in constructor (Core=0, Enrichment=100, Extension=500, Final=1000). - Lookup callbacks must use higher priority than target template.
- Resolve types inside callbacks/lambdas, never directly in constructor.
- Resolve emitted type positions through the Type System APIs:
GetTypeName(...)for model/type references,GetTypeName(templateId, model)for TemplateId-based references, andUseType("Namespace.Type")for framework/external types — including method return/parameter types (e.g.method.AddParameter(UseType("System.Threading.CancellationToken"), "cancellationToken")). Do not useUseType(...)for types represented in the Intent model. - For generated type declarations, put the final class/interface name on the template model/provider (for example
IHasName.Name) and use the same model when resolving references viaGetTypeName(templateId, model); handle name collisions beforeAddClass(...). - Inject DI parameters using
param.IntroduceReadonlyField(). - For not-implemented handlers/method bodies, use:
method.AddStatement("// IntentInitialGen");
method.AddStatement($"// TODO: Implement {method.Name} ({@class.Name}) functionality");
method.AddStatement("""throw new NotImplementedException("Your implementation here...");""");
- Metadata has two uses — know both. (a) Your own cross-step state:
node.AddMetadata("key", value)in one callback, read back in a later one. (b) Reading the designer model the host template already attached: a node generated from a modelled element is stamped with that element under the well-known key"model", somethod.TryGetMetadata<TModel>("model", out var m)tells you which designer element a generated member came from — classes, methods, properties and parameters each carry their own. Guard every read (GetMetadatathrows on an absent key;AddMetadatathrows on a duplicate one). Seeintent-module-orchestrator§ "The Model Bridge".
Must Nots
- Never emit structural C# as raw strings outside the fluent API.
- Never ship an obsolete builder API. Build with warnings visible and treat any
CS0618"…is obsolete" warning as a failure — switch to the replacement the warning names. Known traps:CSharpMethodChainStatement/AddMethodChainStatement;field.WithAssignment(string)(see #5); and passing a base-typedCSharpStatementtoAddStatement(...), which binds to the obsoleteAddStatement(TParent, CSharpStatement, Action<CSharpStatement>)overload — pass astring(collection-initializer items) or the concrete statement subtype (e.g.CSharpObjectInitializerBlock) so the genericAddStatement<TParent, TStatement>overload is selected. - Never add
else/catch/finallyas children of a block (must be siblings). - Never use raw string interpolation for lambda arrows
=>or object initializer braces{}. - Never call obsolete
field.WithAssignment(string)directly (useWithAssignment(new CSharpStatement(...))). AddTypeSource(templateId)is not sufficient for single-file templates.AddTypeSourceenablesGetTypeName(model)resolution for file-per-model templates — it looks up the template instance by model. For single-file templates (one output, no model) likeIIntegrationEventHandler, there is no model to pass, soGetTypeNamecannot resolve the type and the using is never injected. Use the pattern inbuilder-patterns.md§ "Resolving Single-File Template Namespaces" instead.