---
name: zio-golem-code-generation
description: "Generating Scala code in the golem subproject. Use when adding code generation steps, build-time source generators, scalameta AST construction, or sbt/Mill sourceGenerators to the golem/ subtree."
---

# ZIO Golem Code Generation

Guidelines for writing Scala code generators in the golem subproject.

## Core Principle: Scalameta AST, Never String Templates

Always construct generated code as **scalameta AST nodes** using quasiquotes (`q"..."`, `t"..."`, `source"..."`, `param"..."`). Never use string interpolation or text templates to produce Scala source files.

```scala
// ✅ Correct — typed AST
val tree = q"""
  object $name {
    def register(): Unit = {
      ..$registrations
      ()
    }
  }
"""

// ❌ Wrong — string interpolation
val code = s"""object $name {
  def register(): Unit = { ... }
}"""
```

## Shared Codegen Library (`golem/codegen/`)

All build-time code generation logic lives in `golem/codegen/`, a pure (no ZIO, no sbt, no Mill) Scala library that cross-compiles to **Scala 2.12** (for sbt) and **Scala 3.3.7** (for Mill). Both plugins depend on this shared library.

### Cross-compilation constraints

Because the library must compile under Scala 2.12:

- Use `import scala.meta.dialects.Scala213` for the implicit dialect needed by quasiquotes and `.parse[T]` calls.
- For parsing with a specific dialect, use `dialects.Scala3(code).parse[Source]` (explicit dialect application) rather than `implicit val d: Dialect = ...` which causes ambiguity.
- Use `parseMeta[T](code)(implicit parse: Parse[T])` helper pattern for snippet parsing.
- Avoid Scala 3-only syntax in shared code.

### Type and term references

Parse dotted strings into scalameta AST nodes for use in quasiquotes:

```scala
private def parseMeta[T](code: String)(implicit parse: Parse[T]): T =
  Scala213(code).parse[T].get

private def parseTermRef(dotted: String): Term.Ref =
  parseMeta[Term](dotted).asInstanceOf[Term.Ref]

private def parseType(tpe: String): Type =
  parseMeta[Type](tpe)

private def parseImporter(dotted: String): List[Importer] =
  parseMeta[Stat](s"import $dotted").asInstanceOf[Import].importers
```

### API pattern

Generators should expose a **pure, effect-free API** that accepts source text and returns generated outputs + diagnostics:

```scala
object MyCodegen {
  final case class GeneratedFile(relativePath: String, content: String)
  final case class Warning(path: Option[String], message: String)
  final case class Result(files: Seq[GeneratedFile], warnings: Seq[Warning])

  def generate(inputs: ...): Result = {
    // 1. Parse/scan inputs
    // 2. Build scalameta AST via quasiquotes
    // 3. Pretty-print via .syntax
    // 4. Return GeneratedFile with relative path + content
  }
}
```

The plugin wrappers (sbt/Mill) handle file I/O, logging, and build-tool integration.

## Build Integration Pattern

### sbt Plugin (golem/sbt/)

The sbt plugin `GolemPlugin` is an `AutoPlugin` compiled as part of the meta-build via `ProjectRef` in `project/plugins.sbt`. It hooks into `sourceGenerators`:

```scala
Compile / sourceGenerators += Def.task {
  val inputs = scalaSources.map { f =>
    MyCodegen.SourceInput(f.getAbsolutePath, IO.read(f))
  }
  val result = MyCodegen.generate(inputs)
  result.warnings.foreach(w => log.warn(s"[golem] ${w.message}"))
  result.files.map { gf =>
    val out = managedRoot / gf.relativePath
    IO.write(out, gf.content)
    out
  }
}.taskValue
```

For new generators:
1. Add the pure generation logic to `golem/codegen/src/main/scala/golem/codegen/`.
2. Add sbt integration in `golem/sbt/src/main/scala/golem/sbt/`.
3. Hook into `Compile / sourceGenerators` as a `.taskValue`.
4. Use `FileFunction.cached` with `FileInfo.hash` if the generation has an input file (schema, WIT, etc.) to avoid unnecessary regeneration.

### Mill Plugin (golem/mill/)

The Mill plugin `GolemAutoRegister` is a trait mixed into `ScalaJSModule`. It uses `generatedSources` and `T { ... }` tasks. Follow the same pattern as `golemGeneratedAutoRegisterSources`.

### Shared logic, not duplicated

All generation logic lives in `golem/codegen/`. The sbt and Mill plugins are thin wrappers that:
- Collect source files and read their contents
- Call the shared `generate(...)` function
- Log warnings
- Write returned files under managed/generated roots
- Configure build-tool-specific hooks (module initializers, compile dependencies)

When adding a new generation step, implement the logic **once** in `golem/codegen/`, then add thin wrappers in both `GolemPlugin.scala` and `GolemAutoRegister.scala`.

## Existing Code Generation in Golem

### 1. Auto-Registration (shared codegen + sbt/Mill wrappers)

Scans sources for `@agentImplementation` classes using scalameta's parser, then generates `RegisterAgents.scala` and per-package `__GolemAutoRegister_*.scala` files using scalameta quasiquotes.

**Files:**
- `golem/codegen/src/main/scala/golem/codegen/autoregister/AutoRegisterCodegen.scala` — shared logic
- `golem/sbt/src/main/scala/golem/sbt/GolemPlugin.scala` — sbt wrapper
- `golem/mill/src/golem/mill/GolemAutoRegister.scala` — Mill wrapper

### 2. Scala 3 Macros (compile-time, not build-time)

Macros generate code at compile time, not as a build step. They live in `golem/macros/` and use `scala.quoted.*`:

- `AgentDefinitionMacro` — extracts `AgentMetadata` from `@agentDefinition` traits
- `AgentImplementationMacro` — generates implementation wrappers from `@agentImplementation` classes
- `AgentClientMacro` — generates RPC client types
- `AgentCompanionMacro` — generates companion object boilerplate (`get`, `getPhantom`, etc.)

These are **not** build-time code generators. Do not confuse them with `sourceGenerators`.

## Generation Pipeline Shape

Follow this pipeline for new generators:

```
1. Load schema/input    (WIT file, annotation scan, external spec)
2. Parse into models    (typed case classes, not raw strings)
3. Classify/transform   (determine what code to emit)
4. Build AST            (scalameta quasiquotes)
5. Pretty-print         (.syntax on the AST root)
6. Return               (GeneratedFile with relativePath + content)
```

The plugin wrappers handle file writing, formatting, and incremental build integration.

## Conventions

- **Pure functions** — all generator methods are pure. No ZIO, no sbt/Mill types, no file I/O in the shared library.
- **Trait mixin composition** — split generators into traits (`ModelGenerator`, `ClientGenerator`, etc.) and mix them into the main codegen class if complexity warrants it.
- **Dialect-aware** — use `dialects.Scala3` for parsing user sources (with `Scala213` fallback). Use `Scala213` for quasiquote construction (compatible with both 2.12 and 3.x codegen host).
- **Generated file header** — include `/** Generated. Do not edit. */` as a comment in generated objects/classes.
- **Output location** — write to `sourceManaged` (sbt) or `T.dest` (Mill), never to source directories.
