Skip to main content

Make vs Just vs Broski

This page answers one practical question: if your team already uses Make or Just, what changes when you move to Broski?

What problems Broski solves

Traditional task runners are excellent at command orchestration, but they leave core reliability gaps for modern CI and monorepos:

  • timestamp-driven rebuild ambiguity (make can rebuild too much or too little)
  • low explainability for reruns (especially after env or argument changes)
  • no transactional output safety (partial outputs after failed runs)
  • no built-in deterministic content fingerprinting

Broski keeps simple task authoring while adding deterministic cache keys, explainability, and ACID-safe output promotion.

Same intent, different guarantees

Define a build task

build [target] [mode="release"]:
@in src/**/* Cargo.toml
@out dist/{{ target }}
@requires cargo
cargo build --bin {{ target }} --{{ mode }}

Add a local dev task

dev:
@mode interactive
npm run dev

Trigger with arguments

broski build api debug
broski run build --explain

Why teams choose Broski for CI

CapabilityMakeJustBroski
Content hashingNoNoYes (BLAKE3)
DAG graph executionPartial/manualBasicFirst-class
Interactive modeNativeNativeNative
Cache explainabilityNoNo--explain
ACID output promotionNoNoYes
Cache bypass reasoningNoNoYes
Secret-aware explain outputNoNoYes

Why this makes daily developer work easier

  1. Fewer mystery reruns: --explain shows why a task reran.
  2. Fewer broken local states: graph tasks promote declared outputs transactionally.
  3. Less tribal knowledge: contracts (@in, @out, @env, @requires) make intent explicit.
  4. Cleaner migration path: keep Make/Just while porting one pipeline at a time.

Makefile to broskifile conversion examples

Example A: simple build + test

build:
    cargo build --release

test:
    cargo test
version = "0.5"

build:
    @in src/**/* Cargo.toml Cargo.lock
    @out target/release
    cargo build --release

test: build
    @in src/**/* Cargo.toml Cargo.lock tests/**/*
    @out .broski/stamps/test.ok
    cargo test
    touch .broski/stamps/test.ok

Example B: env-sensitive command

release:
    RUSTFLAGS="-D warnings" cargo build --release
release:
    @in src/**/* Cargo.toml Cargo.lock
    @out target/release
    @env RUSTFLAGS=-D warnings
    cargo build --release

Justfile to broskifile conversion examples

Example A: parameterized build

build target mode="release":
  cargo build --bin {{target}} --{{mode}}
build [target] [mode="release"]:
    @in src/**/* Cargo.toml Cargo.lock
    @out dist/{{ target }}
    cargo build --bin {{ target }} --{{ mode }}

Example B: interactive dev workflow

dev:
  docker compose up
dev:
    @mode interactive
    docker compose up

Decision checklist: when to choose graph mode

Use graph mode when:

  • task outputs are meaningful artifacts you want to reuse
  • rerun cost is non-trivial (build/test/package)
  • you need cache explainability for CI debugging

Use interactive mode when:

  • process is long-running (dev server, watcher, REPL)
  • task should stream output directly to terminal
  • caching is intentionally bypassed

Command recipes teams use most

# Validate graph shape and rerun logic without execution
broski run ci --dry-run --explain

# Show dependency layers as text
broski graph ci --format text

# Render graph for visualization in PR review
broski graph ci --format dot > graph.dot

# Force a one-time rebuild, then inspect normal behavior
broski run build --force
broski run build --explain

# Task-level pass-through args
broski test -- --grep smoke

Next reads