No code in the build file.
A build file describes a project. It doesn’t run one. bleep.yaml is data: readable top to bottom by anyone on the team. Logic lives in your code, in your repo, where you can git blame it.
Two decades of Maven, Gradle, and sbt is a long time to watch build tools grow incredibly complex. We built one that won’t. Bleep does precisely what a build is for: compile, test, sourcegen, then package, link, publish what comes out. It refuses the rest. Your container build is code you write. So is your doc generation, your sidecar boot, your CI orchestration. All of it.
A real bleep.yaml. Not pseudocode. Not a marketing render. Plain YAML you can read, grep, diff, and rewrite. The same model bleep itself uses.
$schema: https://raw.githubusercontent.com/oyvindberg/bleep/master/schema.json
$version: 1.0.0-M9
jvm:
name: graalvm-community:25.0.1
projects:
myapp:
extends: template-common
platform:
mainClass: com.example.MainKt
myapp-test:
dependencies:
- io.kotest:kotest-runner-junit5-jvm:5.8.0
- org.junit.jupiter:junit-jupiter:5.10.1
dependsOn: myapp
extends: template-common
isTestProject: true
templates:
template-common:
kotlin:
jvmTarget: '25'
version: 2.3.0
platform:
name: jvm
A project is a project is a project. Code is code is code. Everything explicit, everything simple. Here’s how we got there.
A build file describes a project. It doesn’t run one. bleep.yaml is data: readable top to bottom by anyone on the team. Logic lives in your code, in your repo, where you can git blame it.
No autoplugins, no requires graphs, no Plugin<Project> registration. Bleep doesn’t have one. Code goes in your repo, where you can read it.
Dependencies are a flat list. compile, provided, runtime, Test/test/it/Compile graft a second axis on top of the project graph. Different context? Different project. A project is a project.
There’s no user-definable task DAG. The build does compile, test, sourcegen. Everything else is a script: a main class you call when you want to. Composable like programs, debuggable like programs, no special layer between you and the JVM.
Two reasons, and a rule.
A build plugin activates by rules you didn’t write, mutates settings you can’t see, composes in an order the framework picks. To configure it, you write key-value pairs it documents in a README. To debug it, you reach for println because the build doesn’t host a real debugger. The pitch (one line activates a graph of behavior) sounds easier than writing code. It’s infinitely more complex: opaque, hard to understand, impossible to know what your build will actually do.
Signing, containers, docs, integration sidecars, CI glue: distribution. None of it runs when you save a file. None of it needs a task DAG. None of it should have been wired into the build’s lifecycle in the first place. The inner loop (compile, test, sourcegen) is what the build is for. Everything else is just programs.
Produces files the compiler reads → it’s in the build (sourcegen). Consumes what compile produced → it’s a script. That’s the whole extension surface.
Anything you’ve used a build plugin for falls on one side of that line. The rest didn’t belong in the build to begin with.
Cut the code, the build plugins, the scopes, the task graph. The inner loop stops being something you wait for.
Native CLI binary. Reads bleep.yaml, resolves dependencies through Coursier’s local cache, builds the full project model. Done. No JVM startup, no configuration phase, no “loading projects…” progress bar. The compile daemon (bleep-bsp) is the JVM-heavy bit, and it stays hot between invocations.
Open a project the first time. Switch a branch with a different Kotlin version and reload. In Gradle or sbt that’s a configuration phase, plugin loading, dep resolution, and IDE model rebuild: minutes on real projects. Bleep reads bleep.yaml, builds the BSP model, syncs to the IDE. Initial import: a second or two. Branch reload: milliseconds.
One file changed in a 200-class module. Maven recompiles all 200. Bleep uses Zinc with file-level tracking: one file changed, one (or two) recompiled. The save-to-result loop stays tight.
The same simplification pays off again at CI scale. Build only what changed, pull the rest from cache: two commands, and your CI bill stops being a thing you complain about.
bleep build invalidated compares against a git ref and prints exactly which projects have invalidated state. Scope the rest of your CI run to those. Everything else is already green from the last build.
bleep remote-cache push uploads compile outputs to S3, keyed by a SHA-256 over config plus sources plus transitive deps. bleep remote-cache pull fetches them on the next run. Skip the compile entirely for projects that haven’t changed.
No transparent freshness checks across the network. You push when you want a cache populated, you pull when you want to use it. The fail-hard error model stays clean, your CI logs stay grep-able.
Build-as-data has one more payoff: bleep can rewrite its own input. update-deps, project-rename,templates-reapply: each reads the file, transforms the model, writes it back. No DSL to interpret, no build plugin lifecycle to mutate, just a small library of commands operating on the same model bleep itself uses.
You don’t watch a build tool think about tests for two minutes and get a fifty-thousand-line transcript dumped at the end. You watch suites compile, watch them run, see failures the second they happen, and walk away with a precise summary you can act on.
Test suites run in forked JVMs across every available CPU. Each test project gets its own classpath, its own JVM, its own lifecycle. The bottleneck is your hardware, not the build tool.
The terminal shows which suites are compiling, which are running, which finished, which failed. Failures land the instant they happen, not at the end of the run. Pass --no-tui for plain CI logs.
When the run ends, you get exact suite and test names, pass/fail counts per project, and the diff against the previous run, not a wall of stdout you have to grep through. JUnit XML is one flag away (--junit-report).
Claude Code, Cursor, and the next generation of dev tools talk to bleep through MCP, the Model Context Protocol. One command (bleep setup-mcp-server) and an agent can compile, test, run, and inspect your build through 18 structured tool calls. The design assumptions are blunt: more than one agent will be running at once, tokens are scarce, and any tool that takes seconds blocks every agent attached to it.
Four parallel agents on the same build mean four parallel tool calls. If the build tool is slow, every one of them stalls, aggregate latency multiplies. Bleep’s MCP server runs in-process against the BSP daemon. Every call is sub-second after warmup, sub-100ms once warm.
bleep setup-mcp-server writes.mcp.json in your build root. Any MCP client picks it up automatically. No adapter to configure, no protocol to translate, bleep speaks MCP natively.
Every tool returns a compact JSON summary by default : error counts, failure suites, the diff against the previous run. Full diagnostics are one extra call away. Per-project errors stream as MCP notifications the instant a project finishes, not at the end. Latency floor for surfacing a real problem: milliseconds.
Bleep is open source under Apache 2.0. Java, Kotlin, and Scala on the JVM. Cross-build to JS and Native if you want. Or don’t.