Thank you for your interest in contributing! This guide covers everything you need to get started.
- Python 3.11+ β python.org
- JJ (Jujutsu) β required for running tests (FAVA Trails uses JJ as its VCS engine)
- uv β Python package manager (docs.astral.sh/uv)
fava-trails install-jjThis reuses any already-installed JJ at or above the supported minimum (0.28.0), including newer versions. It never silently downgrades or overwrites a user-managed jj. When installation is needed, it resolves the current official GitHub stable release into the managed path ~/.local/bin/jj (override with --version / JJ_VERSION for reproducible environments). Supports Linux (x86_64, aarch64) and macOS (x86_64, arm64). Make sure ~/.local/bin is in your PATH if you rely on the managed binary. From a source checkout you can also run scripts/install-jj.sh (thin wrapper around the same Python installer). Alternatively, install manually from jj-vcs.github.io/jj. See docs/jj-compatibility.md.
Read this section before you change source, whether you are a person or an agent.
- Preflight. From your checkout, run:
Done when you know every active worktree and remote branch. If another agent or branch is already working in this repository, coordinate with it or use a separate worktree.
git worktree list && git branch -r - Work on a feature branch in your own worktree, for example
git worktree add ../fava-trails-<slug> -b <type>/<slug>. Never commit directly tomain; land changes through a pull request. - Leave installed runtimes alone. Treat any installed or vendored FAVA Trails checkout that a running MCP client launches as read-only. Do not edit it, and do not touch live FAVA Trails instances you did not create. Develop in your own checkout and point a test client at it only as described under "Dog-food locally" below.
- Version bump. Follow docs/version-policy.md: bump affected product versions in the original change PR and run the repository-local updater before review. Documentation, tests, CI and development checks alone need no bump. Editing
AGENTS_USAGE_INSTRUCTIONS.mddoes need one: it is shipped in the package. - Open the pull request with a Conventional Commits title, as described under "PR Expectations".
# Clone the repo
git clone https://github.com/MachineWisdomAI/fava-trails.git
cd fava-trails
# Install dependencies (including dev tools)
uv syncuv run pytest -vTests create temporary JJ repositories in isolated directories β no external data repo required.
uv run ruff check src/ tests/All PRs must pass ruff with zero errors.
- Run the preflight in Working in this repository, then fork the repo (or use a worktree) and create a branch:
git checkout -b fix/my-fix - Make your changes
- Run tests and linting to verify everything passes
- Push your branch and open a pull request
- Tests pass (
uv run pytest -vexits 0) - Lint passes (
uv run ruff check src/ tests/exits 0) - PR title follows Conventional Commits (e.g.,
feat: add X,fix: resolve Y,chore: update Z) β enforced by CI - One logical change per PR β keep PRs focused
- A behavior change updates its owning document and the summaries that repeat it in the same delivery scope; see documentation ownership and the design records in blueprints/. See Maintaining Blueprints for how to choose and update design records.
FAVA Trails is used as a live MCP server, so changes need validation beyond unit tests.
Every PR runs:
- test β
uv run pytest -v - Semantic PR β validates PR title follows Conventional Commits
Both must pass before merge.
After merging to main, point your MCP server at the dev copy to test with real usage:
Restart your MCP client (e.g., Claude Code) and use it for real work. Test the specific changes you made β save thoughts, recall, sync, etc. Use it for at least a working session before releasing.
Important: a client config that uses uv run --directory <checkout> or a
vendor path keeps that tree active even after pip install -U fava-trails.
Run fava-trails version (or fava-trails doctor) in the same environment the
client launches to see package/module version, module path, and MCP SDK version
without printing credentials. See
docs/runtime-and-upgrade.md.
Before tagging, build candidate artifacts from the immutable reviewed commit and run the packaging gates against those exact files:
uv build
export FAVA_CANDIDATE_WHEEL=$(ls dist/*.whl)
export FAVA_CANDIDATE_SDIST=$(ls dist/*.tar.gz)
sha256sum dist/*.whl dist/*.tar.gz | tee candidate-SHA256SUMS
uv run pytest tests/test_packaged_mcp.py tests/test_governance.py tests/test_mcp_protocol.py tests/test_runtime_info.py -v
sha256sum -c candidate-SHA256SUMSUntil PyPI/GitHub release metadata match that candidate, label the work merged but unreleased.
Once dog-fooding confirms the changes work:
A release is either stable, tagged vX.Y.Z, or a release candidate, tagged with
the PEP 440 form vX.Y.ZrcN (for example v0.8.4rc1). In both cases the tag
without its leading v must equal the pyproject.toml version exactly, and
scripts/release-tag.py checks that and derives the channel. A stable Release is
marked latest. A candidate is published as a GitHub prerelease, never latest,
and installers get it only with pip install --pre or an exact pin. The version
is set in the source PR by scripts/mw-version.py (see docs/version-policy.md),
never at tag time, so you tag the version that the reviewed main tip already
carries. A candidate becomes the stable version through a
chore(release): promote version PR. This guide names no version in advance.
-
Set the version in the source PR with
scripts/mw-version.py, not by editingpyproject.tomlby hand -
Merge the PR to
mainafter review and required checks. Merge every change intended for the release now: first publication accepts only a tag on the currentorigin/maintip, so a merge after tagging leaves the tag behindmainand the Release workflow refuses it -
Create and push an immutable tag on the reviewed commit:
git tag vX.Y.Z <sha> && git push origin vX.Y.Z(vX.Y.ZrcNfor a candidate) Do not create the GitHub Release yet β validation must run first. -
Owner runs the Release workflow (
workflow_dispatch, GitHub Environmentfava-release) with inputtagset to the tag from step 3. CI resolves draft-resume state first, then provesrefs/tags/vX.Y.Zpeels to checked-outHEADand either equals current protectedorigin/main(first publish) or is an ancestor of it (draft resume after main may have advanced), builds once, runs packaged gates on the exact wheel+sdist (including sdist install and the upgrade test from the fixed published 0.6.0 baseline), stages or normalizes a draft GitHub Release (withcandidate-SHA256SUMSand verified title/notes/target), publishes those same artifacts to PyPI, verifies published PyPI SHA-256 againstcandidate-SHA256SUMS(fail closed), then undrafts the Release only after that proof (reruns may resume a matching draft after metadata normalize; provenance env is$GITHUB_ENV-inherited, not expression-remapped). Steps 5 to 7 are for the maintainer who owns the vendor copy and the client registration, and they upgrade that runtime to a release you have published. They are not part of feature development, where an installed or vendored checkout that a client launches stays read-only (see "Working in this repository", item 3). -
Update the vendor copy:
cd ~/git/vendor/fava-trails git fetch && git checkout vX.Y.Z
-
Revert
~/.claude.jsonback to the vendor path -
Restart your MCP client
Use the bug report template. Include your JJ version (jj --version), OS, Python version, and steps to reproduce.
For security vulnerabilities, please use GitHub Security Advisories β do not file public issues for security bugs.