Most engineering teams have a code generator somewhere. A script that scaffolds a new service, a template that stamps out a REST endpoint, a CI step that wires up boilerplate. These tools work — until they don’t.

The problem isn’t code generation itself. It’s that most generators operate at the file level. They produce files, not systems. And the moment you need cross-cutting concerns — shared types between services, consistent error handling, aligned deployment configs — the generators start to diverge.

The File-Level Trap

Consider a typical microservices setup. You might have:

  • A Go service generator for backend APIs
  • A TypeScript generator for frontend clients
  • A Terraform generator for infrastructure
  • A Docker Compose generator for local dev

Each one produces files independently. Each one has its own config format, its own conventions, its own edge cases. Over time, these generators drift apart. The Go service uses one error format, the TypeScript client expects another. The Terraform config references resources by naming conventions that only match if you ran the generators in the right order.

This is the file-level trap: generators that produce individual artifacts without awareness of the system they belong to.

Enter NEIR

NAEOS takes a different approach. Instead of generating files, it builds an intermediate representation called NEIR (NAEOS Engineering Intermediate Representation) — a complete model of your system that captures:

  • Modules and their dependency graph
  • Services and their interfaces
  • Architecture patterns and constraints
  • Generation targets and per-language adapters

The pipeline doesn’t go from YAML → files. It goes from YAML → NEIR → validated model → per-language adapters → files. Every output is derived from the same source of truth, with the same validation, the same dependency resolution, the same structural guarantees.

Why This Matters

When you generate from NEIR instead of from templates, several things change:

Consistency becomes structural. Error types, API contracts, and naming conventions are defined once in the spec and enforced across all generated outputs. You can’t accidentally generate a Go service with a different error format than the TypeScript client — they both derive from the same NEIR model.

Cross-language generation is natural. A single spec can produce Go, TypeScript, Python, Java, and Rust code. Not five separate generators — one model, five adapters. Each adapter knows how to express the NEIR concept in its target language.

AI assistants get real context. The NEIR model can be compiled into instruction sets for GitHub Copilot, Claude Code, Cursor, and others. Your AI tools don’t just see individual files — they see the entire architecture, dependency graph, and design intent.

Governance is built in. Policy rules evaluate the NEIR model before any code is generated. Violations are caught at the spec level, not in code review.

The Mental Model Shift

Declarative engineering isn’t about writing less code. It’s about writing code that’s derived from a precise, validated model of what you’re building.

Think of it like this:

TraditionalDeclarative
Write code → hope it’s consistentWrite spec → generate consistent code
Templates produce filesNEIR produces systems
Drift happens silentlyDrift is structurally impossible
AI reads filesAI reads architecture

Getting Started

If you’re curious about the spec-driven approach, the fastest way to understand it is to try it:

# Install NAEOS
go install github.com/NAEOS-foundation/naeos/cmd/naeos@latest

# Create a spec
cat > spec.yaml << 'EOF'
project: my-first-service
modules:
  - name: api
    path: ./api
services:
  - name: rest-api
    kind: rest
    port: 8080
architecture:
  pattern: microservices
generation:
  languages: [go, typescript]
EOF

# Run the pipeline
naeos run --input-file spec.yaml

The output isn’t just files — it’s a complete project structure with dependency-aware modules, typed interfaces, and AI instruction sets, all derived from a single 15-line specification.

What’s Next

We’re working on deeper integrations: LSP support for spec files, a VS Code extension with real-time validation, and tighter cloud deployment pipelines. The spec is becoming the interface for the entire software development lifecycle.

Declarative engineering isn’t a silver bullet. But for teams building multi-language, multi-service systems, it’s a fundamentally better way to think about code generation. Specify once. Build anywhere.