The contribution path for code, infra, deployment recipes, and general docs (legal-substance skills route to skills/CONTRIBUTING.md instead). It covers the quick-start (Mode 1 vs the local Mode 2 profile), the nine-step PR process, DCO sign-off (git commit -s; no CLA, Apache 2.0), per-language code style (ruff, mypy strict for gateway, Prettier/ESLint, SvelteKit-only no-React in web/), the test stack and its five-marker taxonomy, and the port-skills-from-external-sources workflow with its attorney-attestation paragraph and three-place provenance recording. Before opening any engineering PR, or to find the code-style/testing/DCO rules.
Contributing to LQ.AI
Thanks for your interest in contributing. This document covers contributions to code, infrastructure, deployment recipes, and general project documentation. Skills containing legal substance follow a different process documented in skills/CONTRIBUTING.md — that path includes attestation requirements and practicing-attorney review that don't apply to engineering work.
Quick start
git clone https://github.com/legalquants/lq-ai.git
cd lq-ai
cp .env.example .env
# Edit .env with at least one LLM provider API key (or use local profile)
docker compose up -d # Mode 1
# OR
docker compose --profile local up -d # Mode 2
Running backend tests in Docker (recommended when you don't want a local Python venv):
# api/Dockerfile.dev installs [dev] extras (pytest, respx, pytest-cov, …)
# on top of the production image so the test suite runs in-container.
docker build -f api/Dockerfile.dev -t lq-ai-api-dev api/
docker run --rm lq-ai-api-dev python -m pytest tests/ -x
The production image (api/Dockerfile) does not install dev extras to keep
the runtime surface area minimal. Use Dockerfile.dev for any work that requires
running the test suite inside a container.
For backend development without Docker:
# Backend
cd api
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
alembic upgrade head
uvicorn app.main:app --reload --port 8000
# Inference Gateway (separate service)
cd gateway
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
uvicorn app.main:app --reload --port 8001
# Web (OpenWebUI fork)
cd web
npm install
npm run dev
Run the test suite before submitting:
# Python (api/, gateway/)
pytest # unit + integration
pytest -m "not provider" # skip provider-integration tests (require API keys)
ruff check .
ruff format --check .
mypy .
# JavaScript (web/)
npm test
npm run lint
npm run typecheck
What to work on
The project's Deferred Enhancements list (PRD §9) catalogs ~50+ bounded enhancements where the architectural slot exists and the implementation is well-defined. Items tagged P1 are particularly welcome for v1.5+. A few that are well-shaped for first-time contributors:
- DE-013 Saved Prompts Library (S effort) — per-user saved prompts in a sidebar; CRUD plus promote-to-skill.
- DE-015 Voice / Dictation Input (S) — Web Speech API toggle on the chat input.
- DE-031 Reverse proxy / TLS recipes (S) — document Caddy / Traefik / nginx integration patterns.
- DE-103 IP allowlisting and geo-restriction (S) — configurable IP allowlist for the web app and Word add-in.
- DE-105 Outbound proxy support (S) —
HTTPS_PROXY/NO_PROXYconfiguration in the Inference Gateway.
For larger items (M and L effort), please file an issue first to discuss approach before opening a PR. We do not want anyone to invest weeks of work that turns out to conflict with an architectural choice or another in-flight contribution.
For features not on the deferred-enhancements list, please file an issue describing the use case and the proposed approach. Pull requests for unfiled features are welcome but at higher risk of bouncing if they conflict with the project's direction.
Pull request process
- Fork the repository and create a branch from
main. Branch names should describe the work:feat/saved-prompts,fix/citation-engine-empty-result,docs/clarify-tier-config. - Make your changes following the code style and testing requirements below.
- Add or update tests covering the change. Pytest coverage target is 80%; PRs that drop coverage materially will be flagged.
- Update documentation — the PRD, README, skill-authoring guide, or capability docs as relevant. If your change affects user-facing behavior, the docs need to reflect it.
- Sign your commits per the DCO requirement (see Sign-off below). PRs without DCO sign-off cannot be merged.
- Open the PR with a description that explains what changed, why, and how to verify. Link to any relevant issue or DE-### entry in the PRD.
- Respond to review — maintainers will review within ~5 business days for most PRs, faster for security or bug fixes. Substantive review feedback usually requires changes; small style nits can be deferred to follow-ups.
- CI must pass — tests, linting, type checks, container builds, security scans (Trivy, CodeQL). PRs with failing CI are not merged.
- At least one maintainer approval is required to merge. For changes affecting multiple subsystems, two approvals are preferred.
Squash-and-merge is the default; merge commits are reserved for cases where the per-commit history meaningfully aids future debugging.
Sign-off (Developer Certificate of Origin)
LQ.AI uses the Developer Certificate of Origin (DCO) — not a CLA. The DCO is a lightweight assertion that you have the right to contribute the work under the project's Apache 2.0 license. Industry standard, sufficient legal cover, no separate paperwork.
Every commit must be signed off:
git commit -s -m "Your commit message"
This adds a trailer to your commit message:
Signed-off-by: Your Name <your.email@example.com>
The git config user.email you sign off as must match the email associated with your GitHub account or be in your verified emails list. CI validates DCO sign-off on every commit; PRs with unsigned commits cannot be merged.
If you forget to sign off, amend your commits before pushing:
git commit --amend -s --no-edit # most recent commit
git rebase --signoff main # all commits since main diverged
Code style
Python (api/, gateway/, scripts)
- Formatter:
ruff format(Black-compatible). CI rejects unformatted code. - Linter:
ruff checkwith the project'sruff.tomlconfig. Most pyflakes / pycodestyle / pyupgrade / bugbear rules enforced. - Type checker:
mypyin strict mode for the gateway; standard mode for the backend (strict-mode migration is a tracked enhancement, DE-### TBD). - Type annotations: required on all new public functions and class methods. Internal helpers can omit return types where obvious; public API contracts require full annotations.
- Imports: sorted by
ruff(isort-compatible). Standard library first, then third-party, then local. - Docstrings: required on public modules, classes, and functions in the gateway and backend. Format: triple-quoted, summary line, blank line, detail. Descriptive variable names reduce the need for inline comments.
- Async: prefer
async deffor I/O-bound functions; usehttpx.AsyncClientrather thanrequests. Sync helpers are fine for CPU-bound or test code. - Exceptions: raise typed errors from each subsystem's
app.errorsmodule (api/app/errors.py, gateway/app/errors.py) —LQAIErrorand its subclasses. Do not raise bareException. Per ADR 0003, the two subsystems each ship parallel hierarchies; the cross-subsystem contract is the error-code enum in the OpenAPI sketches plus the conformance test in tests/test_error_code_contract.py.
JavaScript / TypeScript (web/)
- Formatter: Prettier with the project's
.prettierrc. CI rejects unformatted code. - Linter: ESLint with the project's
.eslintrc. TypeScript-specific rules enforced for.tsfiles; Svelte rules for.sveltefiles. - TypeScript: required for new files; gradual migration of legacy
.jsfiles welcome but not required for unrelated changes. - Framework: SvelteKit. The
web/codebase is a fork of OpenWebUI, which is a SvelteKit app — extensions and customizations stay in Svelte. Do not introduce React intoweb/. The Word add-in (word-addin/, M3) uses Office.js with React; theweb/codebase does not. - Component conventions: match the OpenWebUI conventions for shared components; use the project's design system primitives rather than ad-hoc Tailwind.
Configuration files
- YAML: 2-space indent. Use anchors and aliases sparingly; prefer explicit duplication over clever YAML.
- TOML: for Python project configuration (
pyproject.toml,ruff.toml). - Markdown: GitHub-flavored. Headings use
#, not===. Wrap at ~100 chars in prose; do not wrap inside code blocks.
Commit messages
- Imperative mood. "Add tier-config endpoints" not "Added tier-config endpoints".
- Subject under ~70 chars; body wrapped at ~72 chars.
- Reference issues in the body:
Closes #123,Refs DE-103. - No marketing language in commit messages. Describe what changed and why; the changelog is for marketing.
Testing requirements
The project takes testing seriously because the substantive correctness of the legal-AI work product depends on it.
Coverage target
Pytest coverage target is 80% across api/ and gateway/. The CI configuration enforces no-decrease coverage on PRs — your PR cannot drop overall coverage. New code should be covered by tests; bug fixes should include a regression test that fails before the fix and passes after.
Test categories
- Unit tests — fast, no external dependencies. Run on every commit. Mock the Inference Gateway, the database, and any provider APIs.
- Integration tests — run against a real Postgres instance (Docker-launched in CI) and the actual Inference Gateway against a mocked provider. These exercise multi-component flows.
- Provider-integration tests — gated behind
pytest -m providerand require provider API keys configured. CI runs these nightly against a small subset; contributors are not expected to run them locally unless their changes specifically affect provider integration. - End-to-end tests — Playwright tests covering happy paths in the web UI. Run on every PR.
- Fuzzing — the Inference Gateway's OpenAI compatibility surface is fuzzed continuously in CI against the OpenAI API spec.
Test stack conventions (Python)
Locked-in choices so new tests don't drift across api/ and gateway/. Changes to these defaults need a PR with rationale.
- Test runner:
pytest. No alternative. - Async tests:
pytest-asynciowithasyncio_mode = "auto"configured inpyproject.toml. Async test functions are written asasync def test_*(...)without a per-test decorator. We pickedpytest-asynciooveranyio's test plugin because the project's runtime is asyncio-only — we never need trio compatibility, andpytest-asynciohas the larger ecosystem. - HTTP client in tests:
httpx.AsyncClientwithASGITransportfor in-process FastAPI tests; noTestClient. Real-network calls in tests are forbidden outside theprovider-marked tests. - Test database: session-scoped fixture runs Alembic migrations once against a per-CI-run Postgres database; per-test fixture wraps each test in a transaction that rolls back on teardown. No truncation between tests; no test-specific migrations. Both subsystems share tests/conftest.py patterns.
- Test data: factory pattern using
polyfactoryfor Pydantic models; no hand-written fixture dictionaries except for canonical examples that stay readable inline. - Provider mocking:
respxfor httpx-level mocks; record-replay fixtures for end-to-end provider flows undertests/fixtures/providers/. - Time and randomness:
freezegunfor time,random.seed(0)at the top of any test that uses randomness. Flaky tests are not tolerated; if you find one, fix it or skip it with a tracked issue.
Test markers
Standard markers — declared in each subsystem's pyproject.toml so pytest --strict-markers will reject typos:
| Marker | Purpose | Default behavior |
|---|---|---|
unit |
Pure unit, no I/O | Always run |
integration |
Hits Postgres, Redis, MinIO, or the Gateway | Run on every PR |
provider |
Hits a real LLM provider; requires API key | Skipped unless -m provider |
slow |
Takes > 5s; usually integration with realistic data | Run nightly, optional locally |
e2e |
Playwright end-to-end | Run on every PR via the Playwright job |
pytest -m "not provider and not slow" is the local-loop default; make test runs this.
JavaScript/TypeScript tests (web/)
The OpenWebUI fork is SvelteKit. New tests use Vitest (Svelte's standard) for unit and component tests; Playwright for end-to-end. Don't introduce Jest unless there's a concrete reason — Vitest is closer to SvelteKit's tooling and has fewer ESM-vs-CJS pitfalls.
- Component tests:
@testing-library/sveltewith Vitest. - Type checking in tests: test files are
.ts/.svelteand passnpm run typecheck. - Snapshot testing: discouraged for component output; preferred for stable structured data (e.g., backend-returned shapes).
When to write what kind of test
| Change type | Required tests |
|---|---|
| Bug fix | Regression test (unit if possible; integration if the bug is multi-component) |
| New API endpoint | Unit tests for the handler logic + integration test exercising the endpoint end-to-end + OpenAPI schema-conformance test |
| New provider adapter | Provider-integration tests (gated behind -m provider); unit tests with mocked provider responses for the request/response shape |
| New skill (engineering side; the skill content itself goes through the skills/CONTRIBUTING.md path) | A worked-example test verifying the skill renders and produces structured output of the expected shape |
| New deployment recipe | Documented steps + a CI verification job that runs the recipe in a clean environment |
| Documentation only | No new tests; CI lints markdown |
Security
If you discover a security vulnerability, please do not file a public GitHub issue. Follow the disclosure process in SECURITY.md:
- Email security@legalquants.com (or the address documented in SECURITY.md at the time you read this) with a description of the vulnerability and reproduction steps. GPG key available at the URL in SECURITY.md.
- We will acknowledge receipt within 72 hours.
- We will fix critical issues within 30 days and coordinate disclosure timing with you.
- After fix and disclosure, the advisory is published on GitHub Security Advisories with credit to the reporter (unless you prefer to remain anonymous).
The project includes safe-harbor language committing not to pursue legal action against good-faith security researchers who follow the coordinated-disclosure process.
For security-affecting changes (not vulnerabilities — changes you want to propose to the project that have security implications), file a normal PR but flag the security implications in the description and tag a maintainer for direct review. Examples: changes to authentication flows, audit logging, the Inference Gateway's tier-derivation logic, the Anonymization Layer, encryption configuration, supply-chain artifacts.
Documentation expectations
If your change affects user-facing behavior, the documentation must be updated in the same PR. The relevant docs:
- docs/PRD.md — the canonical specification. Capability changes go here. PRD edits should follow the existing structure (see PRD's own changelog convention).
- README.md — the front-door doc. Update if your change affects how users get started, what the project does, or the high-level capability list.
- docs/skill-authoring-guide.md — if your change affects how skills work or what's expected of skill authors.
docs/playbook-authoring-guide.md— if your change affects Playbooks.docs/deployment-cookbook.md— if your change affects deployment.docs/compliance/— if your change affects compliance posture (e.g., new audit-log field, new tier-handling logic, new data-flow path that affects a control mapping).docs/security/— if your change affects security posture (e.g., new dependency that affects the SBOM story, new endpoint that affects the threat model).- API changes — the OpenAPI schema is generated from the FastAPI handlers; update handlers, run
make openapito regenerate the schema, and commit the regenerated schema.
PRs that change behavior without updating docs will be asked to add the doc changes before merge.
Porting skills from external sources
Skills may be ported from external corpora (law firm knowledge bases, open-source legal-AI repositories, academic datasets, etc.) if the licence and provenance allow. Porting is a special case of the skill-contribution workflow documented in skills/CONTRIBUTING.md; the additional requirements below apply in addition to the standard skill-authoring steps.
Attestation
Every ported skill PR description must include an attestation paragraph signed by a practicing attorney (the PR author, a co-author, or a named legal reviewer). Template:
I, [Name], a licensed attorney in [jurisdiction(s)], attest that:
- I have reviewed the ported skill against the original source material and confirm the substance is accurately represented.
- The upstream licence ([SPDX identifier]) permits this use under the terms stated in NOTICES.md.
- Any jurisdictional limitations of the source material are reflected in the skill's
jurisdiction:frontmatter field.- This skill does not constitute legal advice and is labelled accordingly in the skill frontmatter.
Pull requests without a complete attestation paragraph will not be merged. Agents do not attest — the human contributor is the attesting party.
Provenance preservation
The upstream source must be recorded in three places:
-
Skill frontmatter — add a
source:key under thelq_ai:extension block:lq_ai: source: uri: "https://example.com/original-skill-repo" license: "Apache-2.0" retrieved_at: "2026-05-14" -
NOTICES.md — add a row to the per-skill provenance table at the repo root. See NOTICES.md for the column format.
-
Commit message — include the upstream URI in the commit body:
Ported from https://example.com/original-skill-repo (Apache-2.0).
Attribution requirements
If the upstream source requires attribution (e.g., a CC-BY licence), the attribution text must appear in:
- The skill's
SKILL.mdunder an "Attribution" heading. - The NOTICES.md row for that skill.
Questions about licence compatibility should be directed to the maintainer team before opening the PR. When in doubt, do not port — create a new skill inspired by the source instead.
Reviewer expectations
Reviewers will look for:
- Correctness — does the change do what it claims to do?
- Tests — does the change have adequate test coverage?
- Style — does the change follow the project's conventions?
- Clarity — is the change readable and maintainable?
- Compatibility — does the change preserve backward compatibility, or if not, is the breaking change documented and justified?
- Security implications — does the change introduce new attack surface, weaken existing protections, or change the data-flow path? If so, has it been thought through?
- Performance implications — does the change add latency or memory overhead in the request path? If so, is the trade-off documented?
- Documentation — has the relevant doc been updated?
Reviewers are also expected to be:
- Constructive — feedback is about the work, not the contributor. Suggest specific changes rather than abstract complaints.
- Timely — initial review within 5 business days for most PRs; follow-up review within 2 days. Communicate if you cannot meet that timeline.
- Decisive — approve, request changes, or comment with a specific question. Do not leave PRs in limbo.
If a PR has been waiting for review longer than the timeline above, please ping a maintainer in the PR or in #contributors on Discord.
Code of Conduct
This project follows the Contributor Covenant. The full text is in CODE_OF_CONDUCT.md. Briefly: be kind, assume good faith, focus on the work, and engage with disagreements substantively rather than personally.
Reports of unacceptable behavior go to conduct@legalquants.com. Reports are confidential and will be reviewed by maintainers; meaningful violations result in escalating consequences from a private warning through to a permanent ban.
License
By contributing to LQ.AI, you agree that your contributions will be licensed under the project's Apache License 2.0. The DCO sign-off (above) is your assertion that you have the right to contribute the work under that license.
Questions?
- General questions → GitHub Discussions or
#contributorson Discord. - Bug reports → GitHub Issues with the
buglabel and a reproduction case. - Feature requests → GitHub Issues with the
enhancementlabel; reference the PRD §9 Deferred Enhancements entry if one exists. - Security → security@legalquants.com (see SECURITY.md).
- Skill contributions → see skills/CONTRIBUTING.md — the skill-specific contribution path.
Thanks for contributing.