A spec-driven harness
Architecture in.
Software out.
An open-source framework that turns architectural specs into working code.
$ uvx ossature
- api.spec · db.spec → ui.spec Spec DAG (UI depends on API & DB)
- Validate ✓ Check constraints
- Audit ✓ audit-report.md
- plan.toml T1: api · T2: db → T3: ui
- Build Tasks in dependency order
- api.ts · db.ts · ui.ts Output files
From Spec to Software
Here's a walk through Qoizig, a QOI image codec in Zig, generated from a single spec file into a working encoder and decoder over 9 tasks.
$ ossature init creates the project config.
Set the output language, pick your LLM model, then
$ ossature new QOI_CODEC scaffolds a blank spec.Write the spec
Describe what the module does in a .smd file —
what it accepts, what it returns, and every error case.
This spec defines the QOI header structure, the pixel hash
function, and both the encode and decode commands.
Run ossature validate to check structure.
# Qoizig
@id: QOI_CODEC
@status: draft
@priority: high
@depends: []
## Overview
A high-performance, zero-dependency command-line tool
and library implemented in Zig for the QOI (Quite OK
Image) format.
## Requirements
### QOI Format Fundamentals
**Header Structure:**
- `char[4]` magic: "qoif"
- `u32` width: image width in pixels (BE)
- `u32` height: image height in pixels (BE)
- `u8` channels: 3 = RGB, 4 = RGBA
- `u8` colorspace: 0 = sRGB, 1 = linear
…
### Encode Command (`qoizig encode`)
**Accepts:**
- `input` (positional, required): Path to source file
- `output` (positional, required): Path for QOI output
**Errors:**
- Input file not found -> print error and exit code 1
- Invalid PPM/PAM header -> print error and exit code 1
…Audit catches the gaps
Before any code is written, ossature audit sends
your specs to an LLM for review. Here it found that
the encode command's examples show two positional arguments,
but only one was documented. You fix the spec, re-run audit,
and move on only when it's clean.
### WARNING: Encode Command
The encode command CLI example shows two positional
arguments (input and output path), but the requirement
only specifies one positional argument.
**Suggestion:** Add `output` (positional) to the
Accepts list for the encode command, and clarify
the default behavior if omitted.Review the plan
The audit produces a build plan — a topologically ordered list of tasks with dependency tracking. Each task generates 1–3 files and includes a verification command. You review and edit this before anything gets built.
# …tasks 001–002: scaffold, types…
[[task]]
id = "003"
spec = "QOI_CODEC"
title = "QOI Encoder Implementation"
outputs = ["src/encoder.zig"]
depends_on = ["001", "002"]
verify = "zig build --summary all"
[[task]]
id = "004"
spec = "QOI_CODEC"
title = "QOI Decoder Implementation"
outputs = ["src/decoder.zig"]
depends_on = ["001", "002"]
verify = "zig build --summary all"
# …tasks 005–009: tests, CLI, integration…Build generates code
Each task gets a narrow context window — only the spec sections,
types, and source files it needs. The encoder task sees the QOI
format spec, the types module, and the build config. Nothing else.
After generation, zig build verifies each step.
Per-task token counts and cost get printed as the build
runs, so you always know what you've spent.
001 ✓ scaffold 2.1k in, 0.4k out, $0.01 002 ✓ types.zig 3.6k in, 0.9k out, $0.02 003 ✓ encoder.zig 5.4k in, 1.8k out, $0.04 004 ✓ decoder.zig 5.1k in, 1.6k out, $0.04 … 9 tasks · 38.4k in, 9.2k out · $0.31
/// Encode raw pixel data into a complete QOI byte stream.
pub fn encode(
allocator: std.mem.Allocator,
pixels: []const u8,
width: u32,
height: u32,
channels: Channels,
colorspace: Colorspace,
) error{ InvalidPixelDataLength, OutOfMemory }![]u8 {
// …validation, buffer allocation…
// Write header
const header = QoiHeader{
.width = width,
.height = height,
.channels = channels,
.colorspace = colorspace,
};
const header_bytes = header.encode();
@memcpy(output[pos .. pos + qoi.QOI_HEADER_SIZE], &header_bytes);
pos += qoi.QOI_HEADER_SIZE;
// Encoder state
var index: [qoi.QOI_INDEX_SIZE]Pixel = // …
var prev_px: Pixel = Pixel.default;
var run: u8 = 0;
// …chunk compression, end marker…
}Explore complete projects
Each example includes the specs, the full build plan, every prompt sent to the LLM, and the generated code — so you can trace every decision from spec to output.
Browse examples on GitHub →The spec is the source of truth.
You only edit the spec. The code is regenerated from it as the project grows.
The spec stays in sync.
Code changes go through the spec, never around it. The spec stays in sync as the project grows, on the tenth iteration as much as the first.
Always on disk.
Prompts, responses, and generated files all get checksummed and committed alongside the code. Nothing disappears when you close the window.
Resume any time.
Builds run incrementally and stop anywhere. Edit the plan, resume tomorrow. There's no conversation context to lose.
Nothing happens by accident.
Spec dependency graph
Specs form a DAG. Build ordering, interface contracts, and cascade invalidation all derive from it. Change one spec and only its dependents rebuild.
Narrow context per task
Each task gets ~2–5K tokens — only the spec sections, types, and source files it actually needs. Focused context produces better output than dumping everything.
Diff-aware incremental builds
Every input is SHA-256 checksummed. Edit a spec and the planner diffs it against the previous plan, keeping unaffected tasks even inside the spec you changed. Cascades stop at interface boundaries.
Full audit trail
Every audit and planner prompt is saved to disk next to the model's response. Files are checksummed. Token usage and cost get tracked per task. When something breaks at task 14, open the directory and see what happened.