Thanks for your interest in contributing! ForgeLM is an open-source project and we welcome contributions of all kinds.
- Bug reports — found something broken? Open a bug report
- Feature requests — have an idea? Open a feature request
- Code — fix a bug, add a feature, improve tests
- Documentation — fix typos, improve guides, add examples
- Notebooks — add Colab notebooks for new use cases
- Config templates — share training configs that worked well for you
git clone https://github.com/YOUR_USERNAME/ForgeLM.git
cd ForgeLM
git remote add upstream https://github.com/HodeTech/ForgeLM.gitpython3 -m pip install -e ".[dev]"Note on the
[dev]extras + coverage gate. Thepytestinvocation inpyproject.tomlcarries--cov-fail-under=40. The[dev]extras are required to reach that floor — installingpip install -e .(no extras) trips the gate because optional-dep test paths can't run. Always usepip install -e ".[dev]"for contributor work. The floor itself is intentional (docs/standards/testing.md); do not lower it.
git fetch upstream
git checkout -b feat/my-feature upstream/mainBranch naming: feat/, fix/, docs/, test/, chore/ + short description.
Edit the code, then run the full validation gauntlet (every guard CI also enforces — passing locally means CI will too):
# 1. Lint + format
ruff format . && ruff check .
# 2. Test suite
pytest tests/ -q
# 3. Config dry-run smoke test
forgelm --config config_template.yaml --dry-run
# 4. Doc-side guards (Wave 4 / Wave 5 additions)
python3 tools/check_bilingual_parity.py --strict
python3 tools/check_anchor_resolution.py --strict
python3 tools/check_cli_help_consistency.py --strictThe four-tool sequence at the top of the gauntlet (ruff + pytest +
--dry-run) is the historical "self-review" command from
docs/standards/code-review.md. The three
doc guards landed in Waves 3-5 and run on every PR via .github/workflows/;
running them locally before pushing avoids CI round-trips.
Push your branch and open a Pull Request against main.
ForgeLM is a single-package layout: a mix of single-file modules and two
focused sub-packages (forgelm/cli/ post-Phase-15 split and
forgelm/data_audit/ post-Phase-14 split) under forgelm/, ~70 test files
under tests/ (collected-test count grows over time — run
pytest --collect-only -q for current), plus configs/, docs/, tools/
(CI guards), and notebooks/. For the authoritative module-by-module map
(purpose, public surface, dependency arrows), see
docs/reference/architecture.md.
# All tests
pytest tests/ -v
# Specific test file
pytest tests/test_config.py -v
# With coverage
pytest tests/ --cov=forgelm --cov-report=term-missingSome tests require torch and are skipped when it's not installed. This is expected in lightweight dev environments.
We use ruff for linting and formatting:
# Check
ruff check .
ruff format --check .
# Auto-fix
ruff check --fix .
ruff format .Configuration is in pyproject.toml under [tool.ruff].
ForgeLM ships a .pre-commit-config.yaml that mirrors
the CI checks (ruff, ruff-format, gitleaks, plus a few hygiene hooks for
trailing whitespace, EOF newlines, and YAML/TOML syntax). The hooks are an
optional ergonomic optimization — CI enforces the same checks on every PR,
so installing them locally is purely about getting feedback before you push.
To opt in:
pip install pre-commit
pre-commit installRun all hooks against the whole tree at any time:
pre-commit run --all-filesIf a hook ever flags a known-good fixture (e.g. a credential-shaped test string the gitleaks hook can't tell apart from a real secret), skip just that hook for the commit:
SKIP=gitleaks git commit -m "test: add credential-shape fixture"CI remains the enforcement boundary; skipping a hook locally never bypasses CI.
- Keep it simple. ForgeLM's strength is simplicity. Don't add complexity unless necessary.
- Config-driven. New features should be configurable via YAML. No hardcoded behavior.
- Optional dependencies. Heavy dependencies go in optional groups:
pip install forgelm[feature]. - Tests required. Every new feature or bugfix needs a test. Keep coverage growing.
- Ruff clean. CI will reject code that doesn't pass
ruff check. - No secrets. Never commit tokens, API keys, or credentials. Use env vars.
If you add a new config field:
- Add the field to the Pydantic model in
config.py - Add it to
config_template.yaml(commented with example) - Update the Configuration Guide if it's user-facing
- Add a test in
tests/test_config.py
- Add the type to
valid_trainersset inconfig.py - Add trainer-specific parameters to
TrainingConfig - Add the TRL config builder in
trainer.py:_get_training_args_for_type() - Add the trainer initialization in
trainer.py:train() - Add dataset format detection in
data.py - Update the wizard in
wizard.py - Add tests in
tests/test_alignment.py - Add a notebook in
notebooks/
We follow Conventional Commits:
feat: add KTO trainer support
fix: handle NaN eval_loss in auto-revert
docs: add GRPO notebook example
test: add merging algorithm tests
chore: update CI to Python 3.13
style: apply ruff format
Look for issues labeled good first issue. These are designed to be approachable for newcomers.
- GitHub Discussions — Ask a question
- Issues — Report a bug or request a feature
By contributing, you agree that your contributions will be licensed under the Apache License 2.0.