A code-first, strongly-typed build automation system for Deno & TypeScript.
[!NOTE] Built with AI. Much of Zuke β code, tests, and docs β was written with AI assistance, then reviewed, type-checked, and tested in CI. Sharing how it was made so you know what you're getting.
[!NOTE] Maturity. Every one of the 54 packages is
1.xand follows full semver β@zuke/core, the@zuke/clicommand, and all the tool wrappers. A minor or patch release never breaks a public symbol; a breaking change bumps the major. See Versioning & compatibility for the pinning guidance and how to diagnose a version mismatch.
Zuke lets you define builds as a TypeScript class. Each target is a class
field declared with a fluent API; targets reference each other by this.x (not
strings), forming a dependency graph that Zuke resolves and runs in topological
order. Inspired by NUKE for .NET. Zuke builds itself this
way β see its own build graph, regenerated straight from
zuke.ts and verified in CI.
- Runtime: Deno
- Packages:
jsr:@zuke/coreplus 50+ typed tool wrappers and plugins and a genericjsr:@zuke/cmdfallback (raw shell viajsr:@zuke/core/shell) β see Packages for the full matrix with published versions - Build file:
zuke.tsin your project root - Zero runtime dependencies
class MyBuild extends Build {
compile = target()
.dependsOn(this.clean, this.restore)
.executes(async () => {
await DenoTasks.check((s) => s.paths("mod.ts"));
});
}
Why Zuke
- Typed, refactor-safe dependencies. You wire targets together with
this.clean, not"clean". Rename a target and every reference moves with it; a typo is a compile error, not a runtime surprise. - Just TypeScript. Your build logic is ordinary async functions with full editor support β no YAML, no bespoke DSL.
- Ergonomic shell. The
$tagged template runs processes with sane defaults (throw on failure, capture output) and is injection-safe. - Small and explicit. A tiny core: discover targets, build a graph, sort, run. No magic, and no plugins to learn for a basic build β the plugin contract is there once you want one.
- Code-first CI. Declare your pipeline in the build with
cicd({ provider: "github" })β the provider is the only required field β and Zuke generates GitHub Actions, GitLab CI, or Azure Pipelines YAML, regenerating it whenever the build runs (and verifying it on CI).
See How Zuke compares for a capability-by-capability
matrix against deno task, npm scripts, Make, Nx, Turborepo, and Dagger, on the
capabilities Zuke was built to provide.
Install
You need Deno installed. The fastest start is the
@zuke/cli tool β install it once, then scaffold a starter zuke.ts, the
./zuke launchers, and a deno.json task into any directory:
deno install -A -g -n zuke jsr:@zuke/cli # once
zuke setup # in your project
./zuke # run the build
See Getting started for the full walkthrough
(scaffolding, the ./zuke launcher, a first build, and GitHub Actions output).
GitHub Actions
The Zuke Build action on the Marketplace is the whole prelude a Zuke job needs β it hardens the runner, checks the repository out, optionally installs Deno, and runs a target, in one step:
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: zuke-build/zuke@v1
with:
target: ci
It goes first, before any checkout of your own: a remote action is fetched by the runner, not from your workspace, which is what lets it install an egress policy before the code that policy governs is ever fetched.
| Input | Default | What it does |
|---|---|---|
target |
"" |
The Zuke target to run. Omit to harden and check out only. |
egress-policy |
audit |
audit records outbound traffic; block enforces allowed-endpoints. |
allowed-endpoints |
"" |
Space-separated host:port list permitted under block. |
persist-credentials |
false |
Leave the token in git config, for a job that pushes. |
fetch-depth |
1 |
Commits to fetch. 0 is the full history, which a secret scan needs. |
ref |
"" |
Branch, tag or SHA to check out. Refused on a secret-bearing event β see below. |
deno-version |
"" |
Install this Deno. Usually unnecessary β the ./zuke launcher bootstraps its own. |
Two things worth knowing before you rely on it:
egress-policystarts ataudit, notblockβ the opposite of harden-runner's own default. That is deliberate, sinceblockwith an empty allowlist fails a build on its first outbound request, but it means the default records egress rather than enforcing it. Run once onaudit, take the endpoint list from the run's insights, then set both.refis refused on an event whose content a contributor writes βpull_request_target,issue_comment,workflow_runand the rest. Those run with your secrets and a writable token, so checking out a ref someone else controls hands them both.pull_requestandpushare unaffected.
Pin the full commit SHA rather than the moving v1 tag when you commit it, the
way you would any other action:
- uses: zuke-build/zuke@<40-character-sha> # v1.0.2
The action section covers the rest. Zuke's own six workflows all open with it, generated from the build β so the version documented here is the version this repository runs on itself.
[!NOTE] All packages publish to JSR from CI via release-please and OIDC (see
RELEASING.md). The npm scope@zukeis not controlled by this project β install from JSR, not npm.
Packages
Zuke ships as a JSR workspace: a tiny core plus a typed wrapper per tool. Every package is versioned and published independently β the badges below track the latest release on JSR.
Looking for the exact API (humans and agents)? Don't guess and don't shell out β every tool is a typed wrapper. The complete, typed surface of every package is in
llms-full.txt(one file), summarised inllms.txt; for a single package rundeno doc jsr:@zuke/<package>. See alsoAGENTS.md.
| Package | Version |
|---|---|
@zuke/core |
|
@zuke/cli |
|
@zuke/cmd |
|
@zuke/deno |
|
@zuke/docs |
|
@zuke/npm |
|
@zuke/security |
|
@zuke/ai |
|
@zuke/console |
|
@zuke/otel |
AI in your pipeline
Zuke ships typed wrappers for the major AI coding CLIs, so you can fold a model into a build the same way you'd run a linter or a test β as a typed target with refactor-safe dependencies.
| Package | CLI | Flagship task |
|---|---|---|
@zuke/claude |
Claude Code (claude) |
run (headless --print) |
@zuke/codex |
OpenAI Codex (codex) |
exec (headless) |
@zuke/gemini |
Gemini CLI (gemini) |
run (headless --prompt) |
Each runs the CLI non-interactively so it fits CI: drive a prompt, pick a
model, constrain the tool set, and capture the response (request JSON for
machine-readable output). Arguments stay a discrete argv array end-to-end β
never a concatenated shell string β so command construction is injection-free,
and API keys ride through the shared .env(...) chainer, backed by a
parameter().secret() build input that Zuke masks in CI output.
import { Build, parameter, target } from "jsr:@zuke/core";
import { ClaudeTasks } from "jsr:@zuke/claude";
class MyBuild extends Build {
apiKey = parameter("Anthropic API key").secret();
review = target()
.dependsOn(this.test)
.executes(async () => {
const out = await ClaudeTasks.run((s) =>
s.prompt("Review the staged diff for bugs in one paragraph")
.model("sonnet")
.allowedTools("Read", "Grep")
.outputFormat("json")
.env({ ANTHROPIC_API_KEY: this.apiKey.value })
);
console.log(out.stdout);
});
}
The mcp (and config/extensions) tasks are flexible command builders for
each CLI's matching subcommand group β handy for provisioning MCP servers in CI.
See Tools for the full task matrix.
AI review and self-healing (@zuke/ai)
Beyond driving the coding CLIs, @zuke/ai makes a
model a first-class citizen of the build graph, two ways:
- AI code review β a reviewer reads the diff, returns a structured assessment (score, severity, findings), writes it to the job summary and the pull request, and breaks the build when the risk crosses a threshold you choose. The output is a typed verdict, not a blob of prose.
- Self-healing builds β attach a fixer to any target with
.recoverWith(...). When the target fails,aiFixerdiagnoses it from the error output and the diff and (diagnose-only default) posts a committable, Copilot-style inline suggestion to the PR. Opt into.autoApply()/.commitFixes()and it fixes the working tree, commits, and re-runs the real command to verify β a fix only counts when the build actually goes green β posting an overview of what it changed instead of a suggestion. - Agent delegation β for open-ended fixes,
agentFixerhands the failure to a coding agent you inject (Claude Code, Codex, Gemini CLI) which edits files itself; one generic fixer, agent chosen at the call site. - Cost controls β a shared
budget(...)caps spend across every reviewer and fixer by an exact token count (no stale price tables; a USD cap is opt-in with your own rates),aiCache(...)reuses a prior response for an identical call, andsuppressions(...)lets you dismiss a false positive by its stable ID so it never fails the build again.
test = target()
.executes(() => DenoTasks.test((s) => s.allowAll()))
// On failure: diagnose, post a committable suggestion, optionally heal.
.recoverWith(aiFixer((f) => f.provider("openai").apiKey(this.key)));
Apply a fixer to every target by overriding recoverWith() on the build.
Safe by default (provider + key only): the fixer writes no files and just
diagnoses. Edits are gated behind a path allowlist, a file cap, and local-only
defaults, and nothing is committed unless you ask. See
AI code review and
Self-healing builds.
Agent skills
Zuke ships agent skills so AI coding assistants set up and author builds the
right way β using the typed *Tasks wrappers instead of guessing the API or
shelling out. Two skills, authored once as portable
SKILL.md folders under skills/:
| Skill | Use it to |
|---|---|
zuke-setup |
Scaffold Zuke into a project (zuke setup, the ./zuke launcher, a first build). |
zuke-write-build |
Write or edit a zuke.ts β add targets, wire dependencies, call tool wrappers, generate CI. |
Claude Code
The skills are packaged as a Claude Code plugin distributed from this repo's marketplace. In Claude Code:
/plugin marketplace add zuke-build/zuke
/plugin install zuke@zuke
That makes zuke-setup and zuke-write-build available β they trigger
automatically when you ask Claude to add Zuke to a project or write a build, and
can be invoked explicitly as /zuke:zuke-setup and /zuke:zuke-write-build.
OpenAI Codex
The same plugin installs into Codex from this repo (it carries a Codex-native
.agents/plugins/marketplace.json and .codex-plugin/plugin.json alongside the
Claude manifests):
codex plugin marketplace add zuke-build/zuke
codex plugin add zuke@zuke
A single skill can also be pulled straight from the repo with Codex's built-in
installer skill, e.g.
$skill-installer install https://github.com/zuke-build/zuke/tree/master/skills/zuke-write-build.
Gemini CLI
The repo doubles as a Gemini CLI extension (the root gemini-extension.json;
Gemini auto-discovers the skills/ folder next to it):
gemini extensions install https://github.com/zuke-build/zuke
Gemini installs a GitHub extension from the repo's latest release, so the
extension tracks releases rather than master. Each release carries a minimal
extension archive (the manifest plus skills/, attached by the release
target), so the install downloads two skills, not the whole monorepo.
The
SKILL.mdcontent is harness-agnostic (the open Agent Skills standard); each manifest above is a thin adapter over the sharedskills/source, so every harness serves the same two skills.
Documentation
Full documentation lives in docs/:
- Getting started β install, scaffold, the launcher, and a first build.
- Core concepts β the build/target/graph model and execution semantics.
- Zuke's build graph β the live dependency graph of
zuke.tsitself, generated by./zuke graphDocand gate-checked in CI. - Parameters β typed build inputs from flags and env
vars (
parameter(),this.x.value). - Authoring API β
target(),Build,run(), code-first CI generation (cicd()), and gotchas. - Run context & cancellation β the
TargetContexta body receives (runId,signal,state) and cancelling a run. - Secrets β source secret values from a manager with
.from(...), with guaranteed redaction from every output. - Service targets β
service()for long-lived processes (a dev server, a database) kept running while dependents execute. - Caching β the incremental build cache
(
.inputs()/.outputs()) and the AI response cache (aiCache). - Durable run state β persist a run's status and per-target
metadata to a pluggable store (
StateStore,ctx.state), with an HTTP API for hosting a production backend. - Cross-run locks β
.lock()claims an exclusive resource across runs and machines, with a TTL backstop and typedLockConflictErrors. - Orchestration: waits β
.waitsFor()suspends a run until an external signal or predicate, saving its state to be resumed later. - Build registry β
zuke registercatalogs a build for dynamic, agentic discovery by an MCP server. - Console output β
@zuke/console: the levelled logger, markup, boxes/tables/rules, and the renderer behind Zuke's own build log. - Shell wrapper (
$) β ergonomic, injection-safe process execution. - Paths (
absolutePath) β the fluent path type. - Tools β the typed tool-wrapper packages and their tasks.
- Installing tools β fetch pinned,
checksum-verified CLIs with
installRelease()andtoolchain(). - Extending Zuke β the plugin contract: lifecycle plugins, tool wrappers, and reusable target bundles.
- Observability (OpenTelemetry) β
@zuke/otelexports run and target spans plus counters as OTLP/HTTP JSON. - MCP server β
./zuke mcpexposes the build to AI agents as typed tools over the Model Context Protocol. - AI code review β gate the build on a structured LLM
assessment of the diff (
@zuke/ai). - Self-healing builds β diagnose and fix failing
targets with
recoverWithandaiFixer, including Copilot-style suggestions. - Using Zuke in a Node/npm project β drive a Node build with Deno.
- Scheduled runs β
triggers.schedule({ cron, tz }) compiled to UTC cron with a daylight-saving wall-clock guard. - CLI reference β commands and flags.
- Programmatic API β drive Zuke from your own code.
- Versioning & compatibility β one semver tier across
every package, the
@zuke/corefloor, and pinning guidance. - How Zuke compares β a capability matrix against
deno task, npm scripts, Make, Nx, Turborepo, and Dagger, on the capabilities Zuke provides.
Development
deno task test # run the suite
deno task cov # run with coverage + enforce the 95% gate
deno task cov:report # print a per-file coverage table
deno task check # type-check
deno task fmt # format (fmt:check to verify only)
deno task lint # lint
deno task spell # spell-check (cspell)
deno task ci # the full gate β deno run -A --frozen zuke.ts ci
deno task ci is ./zuke ci, the same gate the ci job in
.github/workflows/ci.yml runs on every push and
pull request β see AGENTS.md for the full check list.
Contributing
Contributions are welcome! Start with CONTRIBUTING.md for
the full workflow, and please be mindful of our
Code of Conduct.
- Read
AGENTS.mdfor the coding standards (strict typing, noany/as, 95%+ coverage, hermetic tests).CLAUDE.mdis a one-line pointer to it. - Run
deno task cibefore opening a PR β it must be green. - Add tests in the same change as the code they cover.
- Keep commits small and descriptive; update docs when behaviour changes.
Security
As a build tool that runs in other people's pipelines, Zuke treats supply-chain
integrity as a first-class concern: zero runtime dependencies, injection-free
Deno.Command execution, OIDC trusted publishing with provenance,
least-privilege and SHA-pinned CI, a frozen lockfile, and continuous scanning.
Scanning runs as a typed Zuke target β deno task zuke security drives zizmor,
actionlint, and gitleaks through @zuke/security (which
also wraps osv-scanner, semgrep, and Trivy) β alongside CodeQL and OpenSSF
Scorecard for the Security tab.
See SECURITY.md for the full posture and how to report a
vulnerability.
License
MIT β see LICENSE.
Acknowledgements
Zuke stands on the shoulders of giants:
- NUKE and its creator Matthias Koch β the code-first, strongly-typed build model that inspired Zuke. If you build for .NET, use NUKE; Zuke is an homage to its ideas in the Deno/TypeScript world.
- Spectre.Console and its creator Patrik Svensson β the .NET console library whose markup, themes, and rich widgets (rules, panels
No comments yet
Be the first to share your take.