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.