Contributing
Pull requests are welcome. This page gets a checkout of Humanize ready to change: installed, checked the way CI checks it, and with the rules every change is held to. Bugs and feature requests go through the issue forms; for a large change, open a feature request first.
How a pull request is proposed and reviewed is in CONTRIBUTING.md, and who decides, and how to become a reviewer or maintainer, in the organization's GOVERNANCE.md. Everyone taking part follows the Code of Conduct.
First time here?
Your first patch takes one small change from clone to pull request, with every command written out.
Before you start
git and uv. Nothing else: uv sync brings Python with it.
Set up
git clone https://github.com/humanfia/humanize.git
cd humanize
uv sync # ①
uv run pre-commit install # ②uv syncmakes the project's environment in.venv, exactly asuv.lockpins it, Python included.pre-commit installchecks each commit before it is made, with the hooks CI runs.
Run tools through uv run, not uvx, so you get the versions uv.lock pins. To check the setup, run one package's unit tests:
uv run pytest tests/unit/flowsThe checks
uv run pre-commit run --all-files # format, lint, types: seconds
uv run pytest # unit and integration: minutesuv run pytest tests/unit/<package> # the fast loopuv run pytest tests/system/test_flows_ralph.py # real agents, real tokensBoth commands under Before you push must pass, and they are what CI runs. uv run pytest runs tests/unit and tests/integration, and leaves tests/system out.
| Your machine | CI | |
|---|---|---|
| the pre-commit hooks | ✓ | ✓ static, every push and pull request |
tests/unit/ | ✓ | ✓ each package alone, when it changed, on Linux and macOS |
tests/integration/ | ✓ | ✓ each topic alone, when what it runs changed, on Linux and macOS |
tests/system/ | ✓ by hand, a file at a time, when your change needs it | never |
| the macOS app | ✓ on a Mac, when you change desktop/ | ✓ desktop, on macOS, when desktop/ changed |
CI has every job, and how to read a run.
The macOS app
desktop/ is Humanize Desktop, the macOS app: a window on hmz web's page, in Rust with Tauri, held to its spec. Working on it takes a Mac, Rust, Node and pnpm. Built from main, its version is 0.0.0, and it installs nothing: pnpm dev opens it against the hmz on your PATH, and HUMANIZE_HMZ names another, such as this checkout's:
cd desktop
pnpm install
HUMANIZE_HMZ="$PWD/../.venv/bin/hmz" pnpm devTo try how a release installs hmz, build it with the version of a release on PyPI whose hmz web takes --attached: pnpm tauri build --bundles app --config '{"version":"<it>"}'.
Before you push a change to it, run what CI's desktop job runs:
cd desktop/src-tauri
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
cd .. && pnpm tauri build --debug --bundles app,dmgThe disk image a release attaches, for both architectures, is pnpm tauri build --target universal-apple-darwin --bundles dmg, once rustup target add aarch64-apple-darwin x86_64-apple-darwin has run. Finder lays it out, so the first build asks to let your terminal control Finder. Its background is src-tauri/dmg/background.svg, with a plate on opening the app unsigned that a release signed with a Developer ID leaves off; after changing it, pnpm dmg-background remakes background.tiff and background-signed.tiff, the two the bundler takes.
Where a test goes
A test goes in exactly one of three directories, by how much of Humanize it puts together. Its directory is what decides when it runs: no marker to write.
| Holds | Rules | |
|---|---|---|
tests/unit/<package>/ CI | One package of src/hmz/ alone, in the directory named for it: cli, coganchor, daemon, flows, runtime, sdk or tui | Only the package's public names, never a _private name or module. Every other hmz package mocked. No subprocess, socket or network. A job of its own must finish in 3 minutes |
tests/integration/test_<topic>_*.py CI | Packages wired together against fakes this repository wrote: a stand-in CLI, a fake app server, a loopback socket, the mock LLM service | Flat, no subdirectories. The topic is one of core, agents, tui, daemon, anchor and web, a job each, which must finish in 10 minutes |
tests/system/ you | Real agents on real tasks: an installed coding agent CLI, signed in, working on tests/system/sample/, with docker or ssh where the test needs them. And in tests/system/web/, hmz web's page drawn and clicked in real browsers through Playwright, on the stand-ins integration uses | 5 to 30 minutes and real tokens a test; minutes and no tokens in web/. Never in CI |
ruff's SLF001 and pyright's reportPrivateUsage fail a test that reaches a private name. Mock another package where the test imports it, so the test holds this package to what it promises rather than to what the others happen to do.
Run a system test only when your change touches the path it covers, such as a backend's driver, a built-in flow or a machine, and before a release. Never all of them on every change: they take hours together and spend real money. Name the file, as under When a system test covers it above; each is named test_<area>_<what>.py, for flows, harness, machines or tui. A test skips, saying why, where what it needs is not on this machine: a CLI, its sign-in, docker, ssh.
Never copy a sign-in into a test's home
Codex's ChatGPT login and Claude Code's subscription login renew themselves, and a renewal cancels the token the other copies hold: a container or temporary home signed in with a copy of ~/.codex/auth.json or ~/.claude/.credentials.json signs your machine out. A test that needs one runs the CLI as local, or mounts the CLI's own home where it is (--volume=DIR:DIR). The run fails if any file under pytest's temporary directories holds one of those refresh tokens (_copies_no_sign_in in tests/conftest.py).
What the code is held to
pyrightin strict mode, oversrcandtests.# type: ignoreis switched off: a suppression names a pyright rule.ruffwith every rule on, less the onespyproject.tomlgives a written reason for.- Google-style docstrings, and type annotations everywhere.
- Popular, well-maintained libraries before a custom implementation.
- Each layer imports only what the layering table gives it. See Architecture.
specs/is the contract. Change the code to match a SPEC. Do not edit one unless you were asked to.- A change to how flows run also keeps the flows humanfia/flowverse lists working.
- A change to behaviour updates the docs in the same pull request. Working on these docs has the rules.
Commits
Conventional Commits: fix(agents): …, docs(contributing): …, with a ! before the colon for a breaking change. A change and its tests go in one commit.
Next steps
| To | Read |
|---|---|
| make a first, small change end to end | Your first patch |
| add or change a page of these docs | Add a page to these docs |
| find where a change goes, or add a backend or a command | Architecture |
| read what CI ran on a change, and why | CI |
| hold a page to the rules | Working on these docs |