Build automation · Deno & TypeScript

Builds you can refactor, not just run.

Zuke is a code-first, strongly-typed build system. Define targets as TypeScript class fields, wire dependencies with a fluent API, and let Zuke resolve and run them in topological order — with zero runtime dependencies.

Install the CLI
$deno install -A -g -n zuke jsr:@zuke/cli
$zuke setup
$./zuke
import { Build, cicd, run, target } from "jsr:@zuke/core";
import { DenoTasks } from "jsr:@zuke/deno";

class MyBuild extends Build {
  // declare a pipeline → generates .github/workflows
  ci = cicd({ provider: "github" });

  clean = target().executes(() => Deno.remove("dist", { recursive: true }));
  restore = target().executes(() => DenoTasks.cache((s) => s.paths("mod.ts")));

  compile = target()
    .description("Type-check & bundle")
    .dependsOn(this.clean, this.restore)
    .executes(async () => {
      await DenoTasks.check((s) => s.paths("mod.ts"));
    });
}

await run(MyBuild);
v1 core, stabilizing Deno-native Zero runtime deps Published on JSR Inspired by NUKE for .NET
Why Zuke

Everything a build needs — typed end to end.

5the model

  • Typed, refactor-safe dependencies Targets reference each other with this.target — not strings. Rename one and every reference updates.
  • Just TypeScript Ordinary async functions with full types, editor support, and debugging. No YAML, no bespoke DSL.
  • Automatic topological order Declare what each target dependsOn; Zuke resolves the graph and runs everything in order, exactly once.
  • Zero runtime dependencies Ships on Deno with nothing else to install. The launcher installs Deno for you on first run.
  • Extensible by design Extend ToolSettings to build your own typed wrapper for any tool — no fork required.

6run & orchestrate

  • Long-lived service targets Declare a dev server or database with service(); Zuke starts it, waits until ready, and stops it in a finally.
  • Ergonomic, safe shell The $ tagged template runs processes with sane defaults and escapes interpolations to prevent injection.
  • Incremental, remote & parallel Fingerprint .inputs()/.outputs() to skip unchanged work, share a remote cache, and run targets concurrently.
  • Hermetic, pinned toolchains Fetch the CLIs a build drives, pinned by SHA-256, verified and cached — a bare CI runner is reproducible.
  • Drops into Node monorepos Resolves node_modules/.bin npx-style, provisions npm-registry tools with toolchain().npm(), and runs npm workspaces — no npx, no global installs.
  • Secrets, sourced & redacted Pull a secret from 1Password, Vault, or gcloud with .from(execSecret(…)); Zuke redacts it from every log.

3ship & automate

  • Code-first CI/CD Declare pipelines with cicd() and generate GitHub, GitLab, or Azure YAML — verified up to date on every run.
  • A typed tool ecosystem Strongly-typed wrappers for 54+ tools — Deno, npm, Docker, Kubernetes, Vite and more, all on JSR.
  • Announce to chat Post build status to Slack, Teams, and Discord from a target — typed messages with levels, fields, and links.

3ai in the loop

  • AI in your pipeline Typed wrappers for Claude Code, Codex, and Gemini CLI run headless in CI, injection-free.
  • AI code-review gate A model reads the diff, returns a structured risk score, and breaks the build when it crosses your threshold.
  • Self-healing builds Attach a fixer with .recoverWith(); @zuke/ai diagnoses a failure and can apply, commit, and re-run to verify.
Topological resolution

Declare dependencies.
Zuke figures out the order.

Each target lists what it dependsOn. Zuke builds the dependency graph, runs independent targets as far as it can, and executes everything exactly once in the correct order.

  • References are real values — this.build, never a string.
  • Per-target status, timing, and a final build summary.
  • Rename a target and every dependant updates automatically.
  • Incremental caching skips a target whose .inputs() are unchanged — reported cached, with dependents still running.
  • zuke graph --output=html renders an interactive graph you can pan, zoom, and click to trace dependencies.

↓ The graph that builds this website. See Zuke's own build graph rendered to HTML.

Readable output

A build log you can actually scan.

Every target is framed with a ruled banner and closes with a plain pass/fail line and its duration. The run ends with an aligned Build Summary — status and timing per target, a total, and a one-line verdict stamped with the time. It's all powered by @zuke/console, and the same primitives are yours to call in any target.

  • Blocks are easy to scan; a failure prints its error inline.
  • The summary table totals the wall-clock time across targets.
  • @zuke/console gives you tag-based [bold]/[green] markup plus rules, boxes, and tables — a levelled logger for your own output.
  • GitHub Actions gets matching groups, annotations, and a job-summary table.
zuke build
$ ./zuke build

══════════════════════════════
build
══════════════════════════════
  ▸ astro build — 4 pages in 2.2s
 build succeeded in 2.2s

Build Summary
────────────────────────────
Target   Status     Duration
────────────────────────────
clean    Succeeded      0.1s
install  Succeeded     12.4s
check    Succeeded      3.1s
build    Succeeded      2.2s
────────────────────────────
Total                  17.8s

✔ Build succeeded — 4/4 targets in 17.8s · 2026-06-23 07:19

Compose the same output in your own targets

Import @zuke/console and reach for rules, colored markup, boxes, and tables directly — the exact primitives the runner uses for its banners and summary. Read the Console output guide →

zuke.ts
import { ConsoleTasks as Log } from "jsr:@zuke/console";

Log.rule("Release");
Log.info("pushing [bold]core@1.2.0[/]");        // markup: [tag]…[/]
Log.success("[green]✔[/] published 4 packages");
Log.warn("coverage [yellow]94.2%[/] — below gate");

Log.box(["core  1.2.0  ✔", "cli   1.2.0  ✔"], {
  title: "Artifacts",
  border: ["green"],
});

Log.table(
  [{ header: "Package" }, { header: "Size", align: "right" }],
  [["@zuke/core", "18 kB"], ["@zuke/cli", "12 kB"]],
);
output
────────────── Release ──────────────
 pushing core@1.2.0
 published 4 packages
 coverage 94.2% — below gate

 Artifacts ───────────────────┐
  core  1.2.0                
  cli   1.2.0                
└──────────────────────────────┘

Package      Size
─────────────────
@zuke/core   18 kB
@zuke/cli    12 kB
shell.ts
// The $ tagged template runs processes safely — no shell injection.
const sha = await $`git rev-parse HEAD`.text();   // trimmed stdout
await $`deno test -A`;                              // throws on non-zero
const code = await $`flaky-cmd`.noThrow().code();   // never throws
The $ shell

Run processes without the footguns.

The $ tagged template from @zuke/core/shell escapes interpolations automatically, so untrusted values can't break out of a command. Chain .text(), .code(), or .noThrow() for exactly the behaviour you want.

Read the shell guide →
Caching & distributed builds New

Build it once. Reuse it everywhere.

Zuke fingerprints a target's .inputs() and skips it when nothing changed. A remote cache takes that across machines — a fresh checkout or a CI job restores a target's built outputs from a shared store instead of rebuilding them — and --affected narrows a run to just what a change can reach.

  • Incremental, locally.inputs()/.outputs() are fingerprinted; an unchanged target is skipped and shown cached.
  • Remote cache — on a local miss, restore a target's outputs from a shared store instead of rebuilding; upload after a green run so the next machine reuses them.
  • Two backends — a mounted directory or any HTTP object store (S3/GCS/R2), via a typed remoteCache() override or ZUKE_REMOTE_CACHE_* in CI.
  • --affected runs only the targets a git diff can reach, and fanOut emits one parallel CI job per target — the monorepo combo.
Cache backends Filesystem HTTP S3 / GCS / R2
Read the caching guide →
zuke.ts
import { Build, HttpCacheStore, cicd, target } from "jsr:@zuke/core";
import { DenoTasks } from "jsr:@zuke/deno";

class CI extends Build {
  // Restore built outputs on a local miss; upload
  // after a green run so the next job reuses them.
  override remoteCache() {
    return new HttpCacheStore({
      url: this.cacheUrl.value,
      token: this.token.value,
    });
  }

  build = target()
    .inputs("src", "deno.json")   // fingerprint → cache key
    .outputs("dist")              // archived to the store
    .executes(() => DenoTasks.run((s) => s.script("build.ts")));

  // One CI job per target, wired by dependsOn —
  // independent jobs run in parallel.
  ci = cicd({ provider: "github", fanOut: true });
}
remote cache
build · machine A bundled dist/ — uploaded after a green run
Remote store · cache.example.com build @ a3f9c21
build · CI job (fresh checkout) restored dist/cached in 0.0s, no rebuild
AI code review New

Let a model gate the build.

@zuke/ai attaches an AI reviewer to a target as a validation. It reads the diff, asks the model for a structured risk assessment, and breaks the build when the score crosses your threshold — a real verdict your pipeline can branch on, not a blob of prose.

  • Security, secrets, correctness, licensing, or code quality — built-in rubrics, tunable with .criteria().
  • Claude, OpenAI, or Gemini, with the JSON shape enforced server-side.
  • .comment() posts the verdict to the PR/MR — GitHub, GitLab, Azure & Bitbucket.
  • aiReviewWorkflow() generates the CI workflow from your reviewers, for every host.
Works with Claude OpenAI Gemini
Comments & workflows for GitHub GitLab Azure Bitbucket
Read the AI review guide →
🛡 securityReviewer ✘ Build blocked
8/10
HIGH risk score · threshold 7
  • HIGH Hard-coded API token in src/config.ts:42
  • MED Unvalidated redirect in routes/login.ts
  • LOW Stack trace leaked in error response

via @zuke/ai · claude-opus-4-8 · tokens: 1234 in · 567 out · 1801 total

Self-healing builds New

When a build breaks, Zuke fixes it.

Attach a fixer to any target with .recoverWith(aiFixer(…)). When the target fails, @zuke/ai diagnoses it from the exact command, its stderr, and the diff, then proposes a fix — or applies one. The real build command is the verifier: a fix only counts when the command actually goes green.

  • Diagnose-only by default — posts a committable, Copilot-style suggestion to the PR, anchored to the exact file:line.
  • .autoApply() heals it — writes the fix, commits, and re-runs the real command; a bad edit fails the build instead of landing.
  • Safe by default — no files written until you opt in; edits gated behind a path allowlist, a file cap, and local-only defaults.
  • agentFixer hands open-ended failures to a coding agent you inject — Claude Code, Codex, or Gemini CLI — that edits files itself.
Heals with Claude OpenAI Gemini
Read the self-healing guide →
recoverWith · aiFixer
  1. test failed TypeError: name is undefined — src/format.ts:18
  2. aiFixer diagnosing command + stderr + diff → model
  3. fix: guard the null user in formatName() applied · committed a3f9c21
  4. re-running deno test to verify the real command is the gate
  5. healed — 12/12 passing in 1.9s a fix only counts when the build goes green
🤖 Zuke AI fix src/format.ts · line 18
- return user.name.toUpperCase();
+ return (user?.name ?? "anon").toUpperCase();
Suggested change Commit suggestion
zuke.ts
import { Build, parameter, run, target } from "jsr:@zuke/core";
import { DenoTasks } from "jsr:@zuke/deno";
import { aiFixer } from "jsr:@zuke/ai";

class CI extends Build {
  key = parameter("OpenAI API key").secret().required();

  test = target()
    .executes(() => DenoTasks.test((s) => s.allowAll()))
    // On failure: diagnose from the error + diff, post a committable
    // suggestion — or apply, commit, and re-run to verify it goes green.
    .recoverWith(
      aiFixer((f) =>
        f.provider("openai").apiKey(this.key)
          .autoApply()                 // write the fix to the working tree
          .allowPaths("src/**")        // blast-radius allowlist
          .maxEdits(5)                 // …and a file cap
          .commitFixes()               // stage, commit & push to the PR branch
      ),
    )
    .recoverAttempts(2);               // up to two fix-then-rerun cycles
}

await run(CI);

One primitive powers it: a Remediation runs only after a target body fails. Attach a fixer per target, or override recoverWith() on the build to heal every target at once.

MCP server New

Hand your build to an agent.

zuke mcp runs a Model Context Protocol server over your build, so an AI client discovers typed tools — list the targets, inspect the graph, run one with the right parameters — and operates the pipeline through real calls instead of guessing shell commands. Read-only by default; --allow-run opts into execution.

  • Read tools, always onlist_targets, describe_build, and graph expose the whole build surface: the live counterpart of llms.txt.
  • run:<target> with --allow-run — one run tool per target, its input schema derived from your declared parameters (required, enum, number) plus a dryRun flag.
  • Safe by default — execution is off until you opt in; run tools carry MCP's destructiveHint so clients prompt, and .secret() values stay redacted in what the agent sees.
  • Dependency-free — newline-delimited JSON-RPC 2.0 over stdio, implemented by hand; no MCP SDK, no network endpoint.
Works with Claude Desktop Claude Code IDEs Any agent
Read the MCP guide →
shell
# read-only: an agent inspects the build, never runs it
zuke mcp

# also expose run:<target> tools that execute targets
zuke mcp --allow-run

# register with an MCP client (Claude Code shown)
claude mcp add zuke -- deno run -A zuke.ts mcp
zuke · mcp — tools
  • list_targets targets, descriptions & deps read
  • describe_build the full build surface read
  • graph target → deps read
  • run:test executes the target · --allow-run ⚡ run
tools/call { "name": "run:test", "arguments": { "environment": "dev" } }
Built for continuous delivery

A cross-run layer for real deployments.

Durable state, external-event waits, cross-run locks, batch fan-out, cancellation, and governed agent access — the orchestration Zuke layers on top of the typed DAG. All backed by a pluggable state store, with zero overhead until you reach for it.

Durable & resumable

Pipelines that outlive the process.

A target can suspend on an external event — deploy, then wait days for manual approval. The run parks in the state store and the process exits 0; a later zuke resume continues it in a fresh process, exactly once. State persists across the gap, so promoteToProd reads what deployToSit left behind.

  • .waitsFor(externalSignal(…)) suspends; zuke resume <id> --signal continues it.
  • A racing resumer gets a clean AlreadyResumedError — safe to drive from a retrying cron.
  • zuke runs list / show <id> reconstruct a run's full status from the store alone.
Read the orchestration guide →
zuke.ts
awaitTesting = target()
  .dependsOn(this.deployToSit)
  .waitsFor((s) =>
    s.on(externalSignal("testing-approved"))
      .timeout("72h")
      .onTimeout(() => this.rollback));

promoteToProd = target()
  .dependsOn(this.awaitTesting)
  .executes((ctx) => {
    // resumes in a fresh process, days later —
    // the approval payload survived the wait
    promote(ctx.signals.get("testing-approved")?.data);
  });
deploy → wait → promote
$ ./zuke promoteToProd --repo api
 deployToSit   succeeded
 awaitTesting  waiting — signal: testing-approved
  run 6f1c9a suspended · exit 0

$ zuke resume 6f1c9a --signal testing-approved
 promoteToProd succeeded — shipped to prod
zuke.ts
promoteToProd = target()
  // one deploy of a repo at a time — across runs, across machines
  .lock((s) =>
    s.lockKey("deploy", this.repo.value).withTtl("4h")
      .onConflict((h) => `held by ${h.actor}, run ${h.runId}`))
  .executes((ctx) => applyToProd(this.repo.value, ctx))
  .onCancel(() => this.rollback);   // undone in reverse order on cancel
conflict & rollback
$ ./zuke promoteToProd --repo api
 promoteToProd lock conflict
  held by alice, run 6f1c9a since 09:14
  wait, or: zuke cancel 6f1c9a

$ zuke cancel 6f1c9a
 rollback compensated — sit-7 torn down
 run cancelled · lock released
Safe by construction

Serialize deploys. Undo cleanly.

A .lock() claims a resource across runs, processes, and machines — one deploy of a repo at a time. The loser fails fast with the holder's identity and guidance, not a corrupt double-deploy. And .onCancel() registers the inverse of a target, so zuke cancel (or Ctrl-C, or a timed-out wait) unwinds succeeded work in reverse order — reading the exact state each step recorded.

  • TTL + auto-renew: a live holder keeps its lock; a kill -9 frees it after the TTL.
  • Locks release in a finally — success, failure, and cancellation all let go.
  • Compensations run maximally: one that throws is recorded, the rest still run.
Read the locks guide →
Fan-out

One list. N deploys. Honest per-item status.

.forEach() runs the same ordered pipeline over a runtime list — items concurrent, stages sequential per item, with a bounded concurrency. One repo failing doesn't stop the others; the batch fails at the end if any item did. Every item is first-class: deployBatch[api].deploy gets its own summary row and run-record entry, so zuke runs show and MCP report a verdict per repo.

  • Per-item isolation with continueOnItemFailure — no more all-or-nothing batches.
  • The list is an .array() parameter, validated element-by-element before anything runs.
  • The end of JSON-array-in-a-text-field: typed, checked, reportable inputs.
Read the fan-out guide →
zuke.ts
deployBatch = target().forEach(
  () => this.repos.value,          // a validated runtime list
  (repo) => ({                     // an ordered pipeline per repo
    checks: target().executes(() => check(repo)),
    deploy: target().executes((c) => applyToSit(repo, c)),
  }),
  { concurrency: 3, continueOnItemFailure: true },
);
zuke deployBatch
$ ./zuke deployBatch --repos api,web,worker
 deployBatch[api]     succeeded
 deployBatch[web]     failed — checks
 deployBatch[worker]  succeeded
────────────────────────────
 deployBatch — 1/3 items failed
shell
# governed, auditable agent access over authenticated HTTP
zuke mcp --http 0.0.0.0:7777 \
  --allow-run --protect promoteToProd --confirm-destructive

# one server over the whole catalog —
# pipelines registered later appear with no restart
zuke mcp --registry --allow-run
zuke runs show mcp-audit
tool                actor    outcome
───────────────────────────────────
run:deployBatch     alice    ok
signal_run          bob      ok
run:promoteToProd   carol    denied
  reason: operator token required
Governed agent operations

Hand CD to an agent — with a paper trail.

The MCP server grows a streamable-HTTP transport and three authorization tiers: an allow-list of runnable targets, an operator token for protected ones, and a confirm step for destructive calls. Every mutating call is attributed in an audit log. A build registry makes it fleet-wide — one server over a catalog of pipelines that picks up newly-registered builds with no restart.

  • --allow-run=deploy,checks*, --protect promoteToProd, --confirm-destructive.
  • Loopback by default; a non-loopback bind requires a bearer token.
  • zuke register + zuke mcp --registry — new pipelines become callable tools live.
The @zuke ecosystem · 54 packages

Typed wrappers for the tools you already use.

Every wrapper is a fluent, strongly-typed API over a real CLI — published to JSR, tree-shakeable, and refactor-safe. And if one's missing, you can write your own.

Runtimes & package managers

7

Install, run, and publish across every major JS toolchain.

Bundlers & monorepo

5

Bundle apps and orchestrate monorepos from a typed pipeline.

TypeScript runners & compilers

3

Execute, type-check, and compile TypeScript with or without a build step.

Frameworks & code generation

4

Scaffold app frameworks and generate typed clients from a schema.

AI coding, review & self-healing

5

Fold the major AI coding CLIs into a build, gate it on a model-assessed review, or let a fixer heal failures.

Lint, format & quality

7

Keep the tree clean with linters, formatters, and dead-code checks.

Test, coverage & browsers

5

Run unit and end-to-end suites, then upload coverage to your dashboard.

Containers & orchestration

5

Build images and ship to clusters from a typed pipeline.

Cloud & infrastructure

3

Provision and deploy with infra-as-code, typed end to end.

Version control, registry & CI

5

Script Git, GitHub, and publishing straight from your build.

Supply-chain security

1

Scan workflows, secrets, and dependencies as part of the gate.

Engine & plugins

5

The first-party packages that aren't CLI wrappers — the build engine, the global CLI, the process layer, the console, and the OpenTelemetry plugin.

Ship your first typed build in minutes.

Install the CLI, scaffold a zuke.ts, and run your first target.

$deno install -A -g -n zuke jsr:@zuke/cli
$zuke setup
$./zuke --list