z5labs developer-experience monorepo. It bundles two things:
- A collection of Dagger modules under
daggerverse/— reusable, composable building blocks for CI pipelines and local development. - A Claude Code plugin marketplace that ships the same AI tooling we use in-repo, installable into anyone's Claude Code.
Install any module into your own Dagger project with:
dagger install github.com/z5labs/devex/daggerverse/<module>| Module | Description |
|---|---|
bruno |
Run Bruno API collections — a pass/fail gate or a JUnit report — or generate one from OpenAPI. |
certificate-management |
Manage X.509 certificate authorities and issue TLS certificates. |
crypto |
Common crypto utilities — file digests and ephemeral keys. |
dgraph |
Spin up a Dgraph graph-database cluster. |
envoy |
Build Envoy proxy configurations and components. |
flash |
Codeify firmware flashing as Dagger functions. |
go |
Wrap the Go CLI surface (build, test, vet, fmt, run). |
grafana-stack |
Spin up Loki, Tempo, and Mimir as Dagger services. |
java |
Wrap the JVM toolchain — the JDK plus Maven and Gradle. |
kafka |
Spin up a Kafka-wire-compatible cluster. |
oci |
Talk to an OCI registry — push, copy, attach, inspect. |
opentofu |
Drive the OpenTofu (tofu) lifecycle — fmt, validate, plan, apply, destroy. |
otel |
Spin up the OpenTelemetry Collector as a service. |
postgres |
Spin up a single-node PostgreSQL 17 primary. |
qemu |
Boot guest systems under QEMU. |
random |
Generate random values. |
workspace-ci |
Plan change-aware, memoized CI for a workspace of Dagger modules. |
z5labs |
Standardized Go CI and releases: Go checks a source tree, Go.App builds and App.Publish ships a signed, attested multi-arch image. |
zig |
Wrap the Zig toolchain. |
See daggerverse/CLAUDE.md for module conventions
(function caching, code generation, tests layout).
z5labs is the opinionated pipeline the rest of these
modules are wired into. Everything hangs off one entry point:
# check a source tree — gofmt, go vet, golangci-lint, go test -race
dagger -m github.com/z5labs/devex/daggerverse/z5labs call go --source=. ci
# build a multi-arch app, then ship it
dagger -m github.com/z5labs/devex/daggerverse/z5labs call \
go --source=. \
app --version=v1.2.3 --platforms=linux/amd64,linux/arm64 \
with-registry --address=ghcr.io --username="$GITHUB_ACTOR" --auth=env:GITHUB_TOKEN \
with-oidc --request-url="$ACTIONS_ID_TOKEN_REQUEST_URL" --request-token=env:ACTIONS_ID_TOKEN_REQUEST_TOKEN \
publish --repositories=z5labs/hellopublish returns one digest-pinned reference per published tag, and every
published digest carries an SPDX and a CycloneDX document per platform plus a
signed SLSA provenance statement — a publish that cannot produce provenance
fails rather than shipping without it. app also exposes container and
containers, which return the very images publish pushes, so a check can run
against the artifact that ships rather than against a rebuild of it.
The version is yours and is validated as an image tag; the commit comes from
HEAD and from nothing else, so two builds of one (commit, version) pair are
byte-identical. Every image carries one standardized PATH with
/usr/local/bin on it for an extension's executables — see the module's package
doc, which records why both values are fixed.
This repo is also a Claude Code plugin
marketplace named
z5labs-devex. Add it to your Claude Code:
/plugin marketplace add z5labs/devex
Then install a plugin:
/plugin install daggerverse@z5labs-devex
| Plugin | Provides |
|---|---|
backlog |
/backlog:run-backlog — works a repository's story backlog unattended, one issue at a time, from selection through pull request to a label-driven auto merge. |
daggerverse |
/plan-dagger-module — paces a design conversation and drafts story issues for a new daggerverse module. |
hosted-agents |
/hosted-agents:review-plugin — reports which of a plugin's components will work, need setup or break in Claude Code on the web, a Copilot cloud sandbox and a Copilot Actions session, with file:line, remedy and where the fix belongs. |
See plugins/README.md for the plugin layout. To develop
against an unmerged local checkout, run /plugin marketplace add . from the
repo root instead.
A cloud session ignores
the plugins and marketplaces declared in .claude/settings.json, because adding
them needs the workspace trust dialog and a cloud session never shows one.
.claude/hooks/install-plugins.sh declares
them in the session's user settings instead, with the marketplace pointing at
the checkout, so the plugins load from the branch the session is on. It is a
byte-identical copy of the backlog plugin's
assets/install-plugins.sh, which
other repositories get from backlog:setup-backlog;
.claude/hooks/install-plugins_test.sh
fails if the two drift apart.
Claude Code loads plugins when it starts, so in a cloud session the script has to run from the environment's setup script. The hook only declares the settings:
-
The environment's setup script, which runs before Claude Code starts, is what loads the plugins on a new session. They are there from the first prompt. This line is required:
bash /home/user/devex/.claude/hooks/install-plugins.sh /home/user/devex || trueSetup scripts belong to the environment, not the repository, so if the environment also serves other repositories, the line still names this one.
-
The
SessionStarthook in.claude/settings.jsonruns it in every cloud session without any environment setup, and declares the settings only. Claude Code has started by then, so the session cannot be counted on to offer the plugins. A cloud session refuses/reload-plugins, and the next session starts in a fresh container, where the hook is too late again. When the hook is the one that writes the settings, it prints the setup-script line above.
The same script delivers the backlog plugin to a cloud session on any
repository, not only this one. In a repository that declares the z5labs-devex
marketplace rather than being it, the script copies the declared github
source into user settings. When it runs from the environment's setup script, it
also registers the marketplace and installs the plugin with claude plugin
before Claude Code starts. Settings alone are not enough for a marketplace
fetched from GitHub: on a fresh configuration, Claude Code 2.1.316 registered
it on the first start and installed its plugins only on the next, and a cloud
container is fresh every session.
- Prepare the repository. Run
/backlog:setup-backlogin it, from a local checkout with the plugin installed (/plugin marketplace add z5labs/devex, then/plugin install backlog@z5labs-devex). Its "Cloud sessions" step copies the script to.claude/hooks/install-plugins.sh. It merges three entries into.claude/settings.json: the marketplace,backlog@z5labs-devex, and aSessionStarthook that runs the script. It commits nothing, so commit and push both files. - Set up the cloud environment. The report from step 1 ends with the
environment's setup script, written out in full. Paste it into the
environment's setup script.
It does three things, in this order:
- It writes the permission allow-list, and the
autoModestatement if you want one, to~/.claude/settings.json. A cloud session drops thepermissions.allowrules in a repository's.claude/settings.json, because it never trusts the workspace. It keeps nothing in~/.claude/settings.jsonbetween sessions. AndautoModeis read only from user settings. So the setup script, which runs before every session, is the one place these stay in force. - It runs
bash /home/user/<repo>/.claude/hooks/install-plugins.sh /home/user/<repo> || true, so the plugin loads from the first prompt. - It installs the tools the repository's
verifycommands need, at the versions its CI pins. The cloud image's own copies can be too old: on 2026-11-01 itsgolangci-lint2.5.0 refused z5labs/humus's Go 1.27 code..claude/hooks/install-dagger.shis the example fordagger. Allow the hosts each install fetches from under the environment's network access.
- It writes the permission allow-list, and the
- Run it. Start a cloud session on the repository and run
/backlog:run-backlog, or/backlog:next-issuefor one issue.
Two parts of the cycle are local-only, because the session's GitHub proxy
refuses what they need. One is the local reviewer, which mints a GitHub App
token. The other is project-scoped selection (select.project,
--project-value), which reads Projects over GraphQL. So in a cloud session,
copilot is the only reviewer a ["copilot", "local"] roster has. With no
trailing "none", a run there ends in BLOCKED whenever Copilot is
unavailable. Work the backlog from a local checkout until it is back.
A cloud session starts without Dagger or a running container runtime. The
SessionStart hook
.claude/hooks/install-dagger.sh installs
the Dagger CLI at the version pinned by dagger-version in
change-aware-ci.yml, puts it on
PATH, and starts dockerd for the engine, so dagger check runs as it does
in CI.
The environment's network policy has to allow two hosts:
dl.dagger.io, which serves the install script and the CLI.registry.dagger.io, which serves the engine image.
Add them under the environment's Network access → Allowed domains. If either one is blocked, the hook prints a line naming it, and the session starts without Dagger.
Every module has a sibling tests/ module whose suite is a Dagger check.
.github/workflows/ci.yml runs them through
daggerverse/workspace-ci, which plans each change:
it diffs the commit range, works out which modules the change could reach, and
returns one leg per check to run — skipping those a previous run already proved
good — each routed at the module that owns it. Two checks run whatever changed:
the Go SDK's dagger-go-sdk:stale, which proves every committed binding is what
dagger generate writes, and the ci/ module's ci:generated, which
proves that check covers every module and can fail.
The repository is a Dagger v1 workspace: a root dagger.toml, a
dagger-module.toml per module, and a committed dagger.lock that pins every
image digest and the SDK module chain. Every CI job fails if it leaves
dagger.lock changed, so commit whatever entries a new image adds.
The Actions half of that — engine image caching, one dagger check per leg,
recording a pass, and the single status check branch protection requires — is
.github/workflows/change-aware-ci.yml,
a workflow_call workflow anyone can use. ci.yml is a caller of it like any
other repository would be, differing in pointing at the in-tree planner so that a
change to the planner is planned by the changed planner, and in one job of its
own that runs the planner's tests directly whenever a CI file changes. Adopting it
elsewhere is one uses::
jobs:
ci:
uses: z5labs/devex/.github/workflows/change-aware-ci.yml@main
secrets: inheritwith ci / CI Gate as the required check. See
daggerverse/workspace-ci/README.md
for the inputs and the permissions a caller needs.
The manually-triggered
update-dagger.yml workflow bumps the
Dagger pin across every module and opens a PR. Its PR edits
change-aware-ci.yml (a workflow file), which the default GITHUB_TOKEN may not
push, so it authenticates with a
dedicated update-dagger GitHub App via two repository secrets:
| Secret | Value |
|---|---|
UPDATE_DAGGER_APP_ID |
the App's numeric App ID |
UPDATE_DAGGER_APP_KEY |
the App's PEM private key |
Install the App on this repo with Contents, Pull requests, and Workflows
all set to Read and write (Workflows: write is what GITHUB_TOKEN can't
have). The short-lived token is minted per run, so nothing needs rotating.
The scheduled update-dagger-lock.yml
workflow runs dagger lock update every Monday and opens a PR with the refreshed
pins, through the same App: a pull request opened with GITHUB_TOKEN triggers no
CI, and running every suite against the new pins is the point of it.
MIT © Z5labs and Contributors