codegen
Skill67 starsChanged 44 days agoApache-2.0View on GitHub
Documents the Fakt code generation pipeline — immutable AST model, builder DSL, renderer, default value strategies, and extension functions. Use when modifying code generation, changing generated output, adding new generated patterns, understanding builders or renderers, adding a new default value strategy, or debugging generated output. Make sure to use this skill whenever generated Kotlin code needs to change — the pipeline has strict conventions (immutable AST, builder DSL, renderer separation) that must be followed even for small changes.
4 other projects use a version of this.
Adopted by
Most recently taken up by
- vercel-labs/json-rendertheir own version, first seen Oct 29, 2026
- NVIDIA/nvcftheir own version, first seen Oct 13, 2026
- s3s-project/s3stheir own version, first seen Oct 8, 2026
- AterDev/Perigon.CLItheir own version, first seen Sep 3, 2026
Other versions4 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 4
- vercel-labs/json-render★ 19ksame name
- s3s-project/s3s★ 315same name
- NVIDIA/nvcf★ 261same name
- AterDev/Perigon.CLI★ 54same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository10 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- bdd-test-runnerSkill
- commitSkill
- compilationSkill
- feature-optionSkill
- issue-creatorSkill
- kotlin-api-consultantSkill
- prSkill
- sample-scaffolderSkill
- skill-creatorSkill
The file
We split this skill into the rules it gives an agent
1 rule. 0 are common, 0 are distinctive, and 1 is very specific to this project.
0
Common
Most of the 3 edited versions we read keep these.
0
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 31
All codegen files live under codegen-runtime/src/main/kotlin/com/rsicarelli/fakt/codegen/.
“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Codegen Pipeline Guide
The codegen system transforms the shared FakeDeclaration model into Kotlin source files through a layered pipeline: Model (immutable AST) → Builder (DSL construction) → Renderer (string output).
Instructions
1. Understand the Pipeline
FakeDeclaration (from FIR; legacy IR also builds it)
↓
SpecToCodegenMapper (toCodegenSpecs → method/property specs)
↓
FakeGenerator / ConfigurationDslGenerator (orchestrators)
↓
codeFile("pkg") { klass("...") { ... } } (Builder DSL)
↓
CodeFile (immutable AST) (Model)
↓
CodeFile.renderTo(CodeBuilder) (Renderer)
↓
Kotlin source string (written to test source set)All codegen files live under codegen-runtime/src/main/kotlin/com/rsicarelli/fakt/codegen/.
2. Model Layer — codegen/model/CodeFile.kt
Single file, fully immutable value objects. Key types:
| Type | Purpose |
CodeFile | Root: package + imports + declarations |
CodeDeclaration | Sealed: CodeClass, CodeDataClass, CodeFunction, CodeProperty |
CodeMember | Sealed: class body items (CodeFunction, CodeProperty, CodeComment, CodeRegionStart/End) |
CodeClass | Class with modifiers, supertypes, members, constructor properties |
CodeDataClass | Data class with typed properties |
CodeFunction | Function with params, return type, body |
CodeProperty | Property with initializer, getter/setter |
CodeType | Sealed: Simple, Generic, Nullable, Lambda |
CodeBlock | Sealed: Expression, Statements, Empty |
CodeExpression | Sealed: StringLiteral, NumberLiteral, Lambda, FunctionCall, Raw |
CodeModifier | Enum: PUBLIC, PRIVATE, INTERNAL, OVERRIDE, SUSPEND, INLINE, etc. |
ConstructorProperty | Constructor parameter with optional default value |
3. Builder Layer — codegen/builder/
Entry point: codeFile(packageName) { } — returns CodeFile. All builders use @CodeDsl marker.
CodeFileBuilder (top-level):
codeFile("com.example") {
header = "Generated by Fakt"
import("kotlinx.coroutines.flow.StateFlow")
klass("FakeImpl") { /* ClassBuilder */ }
function("fakeFactory") { /* FunctionBuilder */ }
dataClass("CallData") { /* DataClassBuilder */ }
}ClassBuilder — klass(name) { }:
klass("FakeUserServiceImpl") {
public(); internal()
typeParam("T", "Comparable<T>")
implements("UserService") // Simple supertype
implements("Repository", "User") // Generic supertype
annotation("OptIn", "ExperimentalApi::class")
constructorProperty("getBehavior", "(String) -> User?") { private(); defaultValue = "null" }
region("Properties")
property("count", "Int") { private(); mutable(); initializer = "0" }
function("getUser") { override(); returns("User?"); expressionBody = "getBehavior(id)" }
endRegion()
}FunctionBuilder — function(name) { }:
function("getUser") {
override(); suspend(); inline(); operator()
receiver("MyType")
typeParam("T", "Comparable<T>", reified = true)
parameter("id", "String")
parameter("count", "Int", defaultValue = "0")
parameter("items", "String", vararg = true)
returns("User?")
compact = true // no blank line before next compact sibling
body = "return getBehavior(id)" // block body (multi-line: split on \n)
expressionBody = "getBehavior(id)" // = syntax (mutually exclusive with body)
body { buildString { /* lambda */ } } // builder pattern
}PropertyBuilder — property(name, type) { }:
property("name", "String") {
override(); private(); mutable()
compact = true
initializer = "\"default\""
getter = "field.uppercase()" // get() = ... or get() { ... } if multiline
setter = "field = value"
}DataClassBuilder — dataClass(name) { }:
dataClass("GetUserCall") {
public()
property("id", "String")
property("includeDeleted", "Boolean")
}4. Renderer Layer — codegen/renderer/
CodeBuilder — StringBuilder wrapper with indentation:
val cb = CodeBuilder(indentSize = 4)
cb.appendLine("val x = 1") // auto-indented
cb.indent { appendLine("inner") } // increments/decrements level
cb.block("class Foo") { ... } // "class Foo {" + indented body + "}"
cb.build() // final stringRendering.kt — extension functions: CodeFile.renderTo(builder), CodeFile.renderToString(), and render functions for each model type. Key behaviors:
Unitreturn type omitted on expression bodies- KDoc rendered before annotations
- Compact siblings skip blank lines between them
- Multi-line constructor properties when > 1
- Package segments with Kotlin keywords get backtick-escaped
5. Strategy Layer — codegen/strategy/
DefaultValueResolver — composes strategies in priority order:
| Priority | Strategy | Handles |
| 1 | NullableDefaultStrategy | T? → null |
| 2 | PrimitiveDefaultStrategy | String → "", Int → 0, Boolean → false, etc. |
| 3 | StdlibDefaultStrategy | Unit, Flow<T> → emptyFlow(), StateFlow<T> → MutableStateFlow(...), Result<T> |
| 4 | CollectionDefaultStrategy | List<T> → emptyList(), Map<K,V> → emptyMap(), Array<T>, Mutable* |
| fallback | error | "Type '...' requires explicit configuration..." |
To add a new default strategy: implement DefaultValueStrategy interface (supports() + defaultValue()), add to the list in DefaultValueResolver.
6. Extensions Layer — codegen/extensions/
Spec types bridging the FakeDeclaration model to builders:
| Spec | Purpose |
MethodSpec | Method metadata: name, params, return type, suspend, operator, default behavior |
PropertySpec | Property metadata: name, type, isStateFlow, isMutable, default behavior |
AnnotationSpec | Annotation with OptIn detection |
FactoryFunctionSpec | Factory function metadata for fakeXxx {} generation |
Extension functions on builders (in separate files):
| File | Functions | Purpose |
FakeGenerator.kt | generateCompleteFake() | Main fake class generation |
MethodExtensions.kt | overrideMethod(), overrideVarargMethod(), configureMethod() | Method overrides + configure methods |
PropertyExtensions.kt | stateFlowProperty(), behaviorProperty(), suspendBehaviorProperty(), nullableBehaviorProperty(), nullableSuspendBehaviorProperty() | Property overrides + behavior lambdas |
CallTrackingExtensions.kt | callHistoryBackingField(), propertyGetterCallHistoryField(), propertySetterCallHistoryField() | Call tracking fields |
CallHistoryDsl.kt | callDataClass(), verifierClass(), unitVerifierClass(), verifyFunction(), unitVerifyFunction() | Call history data classes + verifiers |
CallHistoryGenerator.kt | addCallHistoryComponents(), generateCallHistoryDeclarations() | Orchestrates all call history code |
FactoryExtensions.kt | generateFactoryFunctionCodeFile() | fakeXxx {} factory function |
TypeParamRegex.kt | eraseTypeParamsToAny(), typeContainsAnyParam() | Generic type erasure utilities |
HistoryStorageInfo.kt | resolveHistoryStorageType() | Resolves call history storage (unit vs data class) |
KDocGenerator.kt | KDoc generation utilities | Generated documentation for fakes |
TypeParamsInfo.kt | Type parameter analysis helpers | Type parameter metadata extraction |
VisibilityExtensions.kt | Visibility mapping helpers | FIR visibility to codegen modifier mapping |
7. Generation Orchestrators — codegen/generator/ (mapper in codegen/analysis/)
| File | Class | Role |
analysis/SpecToCodegenMapper.kt | — | FakeDeclaration.toCodegenSpecs(), toRenderableMethodSpec(), toRenderablePropertySpec() |
generator/ImplementationGenerator.kt | ImplementationGenerator | Produces FakeXxxImpl class + factory function + call history |
generator/ConfigurationDslGenerator.kt | ConfigurationDslGenerator | Produces FakeXxxConfig DSL class |
ImplementationGenerator.generateImplementation() flow:
decl.toCodegenSpecs()→ method and property specsgenerateCompleteFake()→CodeFile(impl class)generateFactoryFunctionCodeFile()→CodeFile(factory)generateCallHistoryDeclarations()→List<CodeDeclaration>(appended to impl file)- Returns
GeneratedFakeCode(implementationFile, factoryFunction)
8. Adding a New Generation Pattern
- Define the output — what Kotlin code should be generated
- Create spec (if needed) — add to
codegen/extensions/or extend existing specs - Add builder extensions — new functions on
ClassBuilderorCodeFileBuilder - Wire into orchestrator — call from
ImplementationGeneratororConfigurationDslGenerator - Add default strategy (if needed) — implement
DefaultValueStrategy, add toDefaultValueResolver - Test — verify with
make publish-local && make test-sample
Related Skills
feature-option— adding options that conditionally enable/disable generationcompilation— validate that generated output compiles
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

