Development
You need Docker and Make. Every lint and test target builds the toolchain image from Dockerfile.dev and runs inside it with your user ID and the checkout mounted at /work, so nothing is installed on the host. make help lists every target. AGENTS.md and .agents/rules/ hold the rules for changes.
Checks
make lint # ruff, pyright, mypy, bandit, shellcheck
make format # ruff format
make test # unit tests, then integration tests
make test-coverage # all tests not marked real, 90 percent branch coverage gate
make sec # bandit and pip-audit, written to sec.sarif
make generate # regenerate owned artifacts; there are none today
make test-coverage is what CI runs. It covers the unit tests in tests/ and the provider-process tests in tests/integration/, writes the total to coverage-percent.txt for the badge, and fails below 90 percent. None of these tests download a model or need credentials.
Tests with real models
These build images or call real services, so CI never runs them:
| Target | What it does |
|---|---|
make test-real | Builds the CPU image, then sends real HTTP requests to downloaded Laya and Von weights. |
make test-real-cuda | The same with the CUDA image and a GPU. |
make test-real-clm | Builds the CPU image and runs the real CLM head against a strict mock embeddings server. Needs the Docker socket. |
make test-real-jev | One live hosted Jev decision. Needs DECIDEALOT_TYPESAFE_API_KEY in your environment and costs a TypeSafe call. |
make build-test, make build-test-cuda | Build an image and check its user, variant metadata, Torch version and provider commands. |
Images and local runs
make build # psyb0t/decidealot:local, :v<version> and :latest
make build-cuda # the same tags with -cuda
make run # build, then docker compose up -d
make run-cuda # build the CUDA image, then Compose with the CUDA override
make status # docker compose ps
make stop # docker compose down
make run needs DECIDEALOT_MODEL_DIRECTORY in .env; see Compose.
Dependencies
Application dependencies are pinned with == in pyproject.toml and locked in uv.lock. Change them only through the Make targets, which apply the DEPENDENCY_CUTOFF age gate from the Makefile:
make pkg-add PKG=name==1.2.3
make pkg-update PKG=name==1.2.4
make pkg-remove PKG=name
make pkg-upgrade
Model runtime dependencies live in requirements-providers-cpu.in and requirements-providers-cuda.in. make model-lock compiles them into the hash-locked .txt files the images install. make dep refreshes both kinds.
Documentation
The site is built from docs/ with the same Jekyll image GitHub Pages uses:
make docs
The output goes to .testing/docs-site. Every page needs front matter with a unique title and a description. Link pages to each other by relative .md path and link repository files by full https://github.com/psyb0t/decidealot/blob/main/... URL.
CI
.github/workflows/pipeline.yml runs on every push:
| Job | Runs on | What it does |
|---|---|---|
code | Every branch push | make lint, make test-coverage, make sec and make generate. Uploads the SARIF report and fails if make generate changes a tracked file. |
call-docker-image-workflow | Pushes to main | Builds both images for linux/amd64 and pushes :latest, :latest-cuda and :sha-<commit> tags, then scans them with Grype. |
badges | main and tags | Updates the coverage, version and license badges on the badges branch. |
wait | v* tags | Waits for the main run of the tagged commit to succeed. |
docker-promote | v* tags | Points the release tags at the images that main run built, without rebuilding. |
release | v* tags | Creates the GitHub Release. |
publish-to-clawhub | v* tags | Publishes the decidealot skill and the decidealot OpenClaw bridge. |
publish-to-mcp-registry | v* tags | Publishes server.json with the release version. |
Releases
A release bumps version in pyproject.toml and .agents/.codex-plugin/plugin.json, adds a CHANGELOG.md entry, and tags vX.Y.Z on a commit that is already on main. make version prints the current tag.