Skip to content

Docs / Stacks

Stacks

How a repository is recognised, the commands each stack is seeded with, what a setup pass writes, and the exit codes a check script uses to say it could not grade.

When developerz.ai onboards a repository, it reads the marker files at the top of the checkout, names the stack, and seeds three commands from that name: bin/setup installs, bin/dev runs the app, bin/check proves a change. This page lists the markers, the commands each stack is seeded with, what the setup pass writes into your repository, and the exit codes bin/check can use to say "I could not grade this" instead of "this change is broken".

Why the list exists, and what a repository outside it still gets, is on Your stack. The policy file that bounds what the agent may do has its own page: the maintainer.yml reference.

Stack detection

Detection reads the names of the files at the top level of the checkout. It runs nothing and fetches nothing. The first row that matches wins:

Order Stack Marker
1 Ultimate app.config.ts, plus an @ultimat3/* package in dependencies or devDependencies
2 Bun bun.lock or bun.lockb
3 Node package.json
4 Python pyproject.toml, requirements.txt or setup.py
5 Go go.mod
6 Rust Cargo.toml
7 Ruby Gemfile
Unknown none of the above

Ultimate needs both halves: app.config.ts alone is also the root config of SolidStart and TanStack Start, and those repositories stay Bun or Node.

Ruby is last on purpose. A Gemfile is often auxiliary (Jekyll docs, fastlane or danger beside a mobile or CI build), while package.json, pyproject.toml, go.mod and Cargo.toml never are, so a Go repository with a docs Gemfile stays Go. The same order means a Rails application that carries a package.json is detected as Node, and its commands are corrected the way any other stack's are (below).

Unknown, and commands your model writes

A repository outside every row is recorded as unknown, not pushed into the nearest row. The table is a floor, not the answer: during onboarding your account's own model reads the repository, runs its commands, and proposes all three. That works for any stack, recognised or not. A proposal is refused whole, and the floor kept, if any command is missing, runs past one line, or carries something shaped like a credential.

An unknown repository with no accepted proposal gets placeholders that exit 1 until someone fills them in:

echo "TODO(setup): install dependencies"; exit 1
echo "TODO(setup): run the app"; exit 1
echo "TODO(setup): typecheck + lint + test"; exit 1

Seeded commands

Stack bin/setup bin/dev bin/check
Bun bun install bun dev bun run typecheck && bun run lint && bun test
Node npm ci npm run dev npm run lint && npm test
Python python -m venv .venv && ./.venv/bin/pip install -r requirements.txt python -m app ruff check . && pytest
Go go mod download go run ./... go vet ./... && go test ./...
Rust cargo fetch cargo run cargo clippy -- -D warnings && cargo test
Ruby bundle install bundle exec rackup bundle exec rubocop && bundle exec rake test
Ultimate bun install bunx x dev bunx x build --target static && bunx x verify

For every stack except Bun and Ultimate, the generated bin/setup first runs command -v mise >/dev/null && mise install, so a toolchain version the repository pins (mise.toml, .tool-versions, .ruby-version) is installed without root before the setup command runs.

Monorepos. A workspace root (a pnpm-workspace.yaml or go.work file, a workspaces field in package.json, or a [workspace] table in Cargo.toml) widens bin/check for two stacks. Node becomes npm run lint --workspaces --if-present && npm test --workspaces --if-present and Rust becomes cargo clippy --workspace -- -D warnings && cargo test --workspace. Bun, Go and Ultimate already cover every member from the repository root.

When each script runs

  • bin/setup runs before any work, at the start of every session.
  • bin/check is the gate. A worker proves its own pull request with it, and deep review runs it over a diff. A repository with no bin/check is graded with its stack's check from the table above (Ultimate apps ship their own bin/check); an unknown repository with no bin/check has no gate, so nothing is graded and nothing is graded red.
  • bin/stop runs when a run ends, on every exit path. It is not part of the trio and gates nothing, but it must be idempotent, exit 0 when nothing is running, and never remove volumes or data. Whether or not you ship one, the runner also stops (never removes) any Compose container whose project directory is inside the checkout.

A repository that never wrote a bin/stop gets docker compose stop when a Compose file (compose.yml, compose.yaml, docker-compose.yml or docker-compose.yaml) sits at the root, and true otherwise. One that already has bin/setup, bin/dev and bin/check but no bin/stop gets none: setup never back-fills a teardown beside a gate that was already there.

What a setup pass writes

Setup never overwrites a file you wrote. Every file it creates is written only if that path is absent, so your own scripts, agent files and config survive verbatim. Two files are edited rather than created: .gitignore gets lines appended, and .mcp.json gets the platform's MCP servers added under names it does not already use (a malformed .mcp.json is left untouched). The one file it replaces is its own onboarding scorecard, which each run rewrites.

What it may create:

  • CLAUDE.md, with AGENTS.md as a pointer to it, and a specialist roster, skills and commands under .claude/agents/, .claude/skills/ and .claude/commands/.
  • bin/setup, bin/dev, bin/check and bin/stop, from the table above or from your model's accepted proposal.
  • A proposed policy at .dz/maintainer/maintainer.yml and a reviewer prompt at .dz/maintainer/reviewer.md.
  • .github/workflows/ci.yml running bin/check, only in a repository with no CI of its own. A repository that already has CI keeps it and gets a proposal to align it instead of a second pipeline.
  • .githooks/pre-commit running the same bin/check, only when no hook manager (husky, lefthook, pre-commit, simple-git-hooks) is already present.
  • mise.toml, only when the repository pins a toolchain version somewhere and has no mise config yet.
  • .env.example with the names of the variables the pass learned about, never their values, and only when it learned some.
  • docs/README.md and an onboarding scorecard at .dz/onboarding/scorecard.md.

None of it is pushed to your default branch. It lands as one pull request from the dz/onboarding branch, and merging that pull request is the opt-in.

A new project from nothing is a different path: a framework scaffold only runs on a repository that holds nothing but README.md, LICENSE and .gitignore. A .github/ directory counts as content, so a repository with only issue templates is never scaffolded over.

Gate exit codes

bin/check can tell the platform it could not grade the change, and that is never recorded as red. The exit code decides:

bin/check result Graded as
exit 0 green
exit 75 could not grade: a precondition is unmet
exit 126 or 127 could not grade: the shell could not run the command (126 not executable, 127 not found)
exit 129 to 255, or killed by a signal could not grade: the gate was killed
ran past the time limit could not grade: timed out
any other exit, including 1, 2 and 128 red

The box can also decline to grade on its own account, when the gate hit a memory limit or the box was too loaded to start it. Those are never red either.

Exit 75: a precondition is unmet

Exit 75 is how your bin/check says a resource it needs (a database, Redis, a credential) is unreachable from this box. On exit 75 the platform runs your bin/setup once more and runs bin/check once more. If the second run grades, that verdict stands; if it refuses again, the change is recorded as "could not grade", never as a failing change. A repository that never exits 75 loses nothing: its gate is graded exactly as before.

#!/usr/bin/env bash
set -euo pipefail
if ! pg_isready -q; then
  echo "postgres is unreachable; refusing to grade" >&2
  exit 75
fi
bun run typecheck && bun run lint && bun test

Why 75:

  • It is EX_TEMPFAIL from sysexits.h: "the request could not be handled right now, try later", which is exactly what an unreachable store is.
  • None of the tools in the seeded commands exits 75 on a failing test, lint or type error, so a real failure never reads as "could not grade".
  • 126 and 127 belong to the shell and cannot carry a second meaning.
  • Exit 2 is deliberately not in the contract: tsc exits 2 on real type errors, so 2 is red and stays red.

It is an exit code and not a message because exit 1 is ambiguous: the same script can exit 1 for "the store is unreachable, refusing to run" and for a failing test, and the platform never matches your script's output to tell them apart. Your wording can change without notice; the exit code is the contract.