Contributing
This page is the functional path from zero to a merged pull request. Every
command below is verified against the current repo. The exhaustive policy lives in
CONTRIBUTING.md;
the design why lives in the ADRs.
The path at a glance
- Clone and see it run — the demo, one command.
- Set up for development — tools, build, gates.
- Know the quality bar — TDD, A+, GoDocs.
- Pick or open an issue.
- Fork → branch → TDD → PR.
- Pass the CI gates.
1. Clone and see it run
The full stack — Postgres, Redis, and the control plane with the embedded Airflow 3.2.1 UI — runs from a single Compose profile. No Go or Python toolchain needed for this step, just Docker.
git clone https://github.com/neochaotic/leoflow.git
cd leoflow
docker compose --profile demo up --build
Then open http://localhost:8080 and log in as admin@leoflow.local /
admin. Stop with docker compose --profile demo down (add -v to wipe data).
One process serves the API (/api/v2), the internal UI API (/ui/*), and the
React UI (ADR 0017). It auto-applies migrations and seeds the admin user on
first boot. The three operating modes (Lite · Pro-soon · Demo) are
described in Operating modes.
2. Set up for development
cp .github/CLAUDE.md.template ./CLAUDE.md # optional (Claude Code; gitignored)
make setup # Go tools, Python parser/runtime, and the pre-commit hook
make build # bin/leoflow, bin/leoflow-server, bin/leoflow-agent
make dev-up # start Postgres + Redis (Docker) and apply migrations
make lint test # the gates you must pass before pushing
For a full author→run loop without Kubernetes, leoflow lite runs an isolated,
hot-reloading stack with the silver Lite edition badge (see
Operating modes):
make dev-install # put leoflow + server + agent on your PATH
leoflow lite provision # check/provision dev dependencies
leoflow init dags/my_dag # scaffold a project
leoflow lite dags/my_dag # hot-reload at http://localhost:8088 (Lite edition)
3. The quality bar (non-negotiable)
These are enforced by the pre-commit hook and CI — a PR that misses them cannot merge. They are not bureaucracy; they are why the codebase stays an A+.
- Strict TDD — every line of production code is written against a failing
test (red → green → refactor). The preferred shape is two commits: a
test:commit that fails, then thefeat:/fix:that makes it pass. (ADR 0011) - Go Report Card A+ — gofmt, govet, gocyclo ≤ 15, golint, ineffassign, misspell,
license, plus the extended golangci-lint stack.
make lintclean every commit. (ADR 0012) - GoDocs on every exported identifier — starting with the name, ending with a period.
- English for all code, comments, commit messages, and docs.
- No new dependency without justification; the supply-chain scans (govulncheck, gosec, Trivy, CodeQL) run on every PR. (ADR 0014)
A change that contradicts an ADR is rejected unless the ADR is first amended in a separate PR. When in doubt, open an issue and ask before coding.
4. Pick or open an issue
Browse open issues. Good entry
points are labelled
good first issue
and help wanted.
Comment on the issue to claim it before starting, so effort isn’t duplicated.
Open a new issue and pick Bug report. The form asks for repro steps, how you’re running Leoflow (Lite / Pro / Demo), and environment — fill it in fully so we can reproduce.
Open a new issue and
pick Feature request. For anything architectural or cross-cutting, also open
a PR adding a draft ADR under website/content/project/adrs/ with status Proposed — the design
discussion happens there.
For anything beyond a small bug fix, open or comment on an issue before writing code. This avoids misaligned designs and wasted effort.
5. Fork → branch → TDD → PR
# 1. Fork on GitHub, then clone YOUR fork and add the upstream remote
git clone https://github.com/<you>/leoflow.git
cd leoflow
git remote add upstream https://github.com/neochaotic/leoflow.git
# 2. Branch from an up-to-date main
git fetch upstream && git switch -c fix/clear-error-message upstream/main
# 3. Write the FAILING test first, watch it fail, then implement
# (commit the test, then the code — red → green → refactor)
make lint test
# 4. Record what changed, as a fragment that cannot conflict with anyone else's
make changelog # or: changie new
# 5. Push to your fork and open the PR
git push -u origin fix/clear-error-message
Branch names:
feat/…,fix/…,docs/…,test/…,refactor/…,chore/….Commits: Conventional Commits —
feat: add XCom schema validation,fix: handle pod OOMKilled,test: failing test for retry backoff.One logical change per PR. If you write “and also…” in the description, split it.
Opening the PR loads a template — fill in what changed, how you tested, and tick the checklist (TDD, lint, GoDocs, ADR compliance).
The changelog entry is a file, not an edit.
make changelog(a wrapper aroundchangie new) asks for a kind and a one-line body and writes.changes/unreleased/<slug>.yaml. Commit it with your change; the release cut assembles every pending fragment intoCHANGELOG.md. Write the body for the person who will read the release notes: what an operator or DAG author will notice, with the issue number.This exists because
CHANGELOG.mdhas one## [Unreleased]section, so every open PR edits the same few lines: each merge conflicts the rest, each conflict costs a rebase, and each rebase re-runs the full CI matrix. Two fragments are two different files and cannot conflict. Hand-editingCHANGELOG.mdstill passes the gate; the fragment is the cheaper way to do the same thing.A PR with no user-facing change (release prep, chore, dependency bump, docs-only) takes the
skip-changeloglabel instead. Dependabot PRs are exempt without one.
Changes to internal/auth/, internal/executor/, internal/storage/,
migrations/, proto/, or anything touching credentials/secrets are not
accepted from first-time contributors — open a discussion issue first.
6. The CI gates
Every PR runs, and must pass: the build + unit/integration tests with the
per-package coverage floor, golangci-lint (the A+ stack), and the
security suite (govulncheck, gosec, Trivy, CodeQL, gitleaks), and the
CHANGELOG guard (a fragment, a hand-written entry, or the skip-changelog
label). The same
make lint test you run locally is the fast feedback loop; CI is the source of
truth. Push fixes until everything is green, then a maintainer reviews — we aim
for three business days.
By contributing you agree your work is licensed under Apache 2.0. Thank you for helping make Leoflow better.
Editing the docs
This site is Hugo + Docsy, and its source is website/. Install Hugo
extended (0.110.0 or newer; CI pins 0.165.0) and
run npm ci once inside website/ for the theme’s PostCSS toolchain, then preview
with live reload:
cd website
hugo server # serves at http://localhost:1313/leoflow/ (the subpath matters)
The CLI and Go references are generated, not written: website/scripts/gen-cli.sh
renders content/reference/cli/ from Cobra, and gen-go.sh renders
content/reference/go/ from gomarkdoc. Both trees are committed so a fresh clone can
preview without those toolchains, but CI reruns every generator before it builds, so
a hand edit to either tree never reaches the published site. Change the command
definition or the GoDoc instead.
Build the docs covers the rest: the other two generators (OpenAPI and the Python runtime API), the redirect aliases, and the production build.