Thank you for your interest in contributing! This guide will get you set up and explain how to submit good contributions.
- Code of Conduct
- Getting Started
- Project Structure
- Development Setup
- How to Contribute
- Pull Request Process
- Coding Standards
- Testing
- Commit Messages
This project follows the Contributor Covenant Code of Conduct. By participating, you agree to uphold these standards.
Before diving in, please:
- Search existing issues and PRs before opening a new one — duplicates slow everyone down.
- Open an issue first for any non-trivial change (new feature, architectural change, breaking fix). This prevents wasted effort if the direction isn't aligned.
- For small, obvious fixes (typos, dead links, clear bugs) you can go straight to a PR.
researchos/
├── backend/ # Python FastAPI backend
│ ├── app.py # Entry point — CORS, routers, startup seeding
│ ├── models/ # Pydantic domain models (all inherit CamelModel)
│ ├── services/ # Business logic + all Supabase access
│ ├── routers/ # Thin FastAPI route handlers
│ ├── agents/ # pydantic-ai agent definitions
│ └── migrations/ # SQL migration files for Supabase
└── frontend/ # React 18 SPA (Vite)
└── src/
├── services/api.js # All API calls — never fetch directly in components
├── context/ # React context (active library state)
├── components/ # Shared UI components
└── pages/ # Route-level page components
See ARCHITECTURE.md for a detailed walkthrough.
git clone https://github.com/<org>/researchos.git
cd researchoscp backend/.env.example backend/.env
# Edit backend/.env and fill in your keys:
# OPENAI_API_KEY=sk-...
# SUPABASE_URL=https://<project-ref>.supabase.co
# SUPABASE_KEY=<anon-key>Open the Supabase SQL editor and run backend/migrations/schema.sql — this single file creates the complete schema in one shot.
If you prefer to apply migrations incrementally (e.g. against an existing database), the numbered files in backend/migrations/ can be run in order instead.
cd backend
uv sync --group dev
uv run uvicorn app:app --reload --port 8000cd frontend
npm install
npm run devOpen http://localhost:5173. Vite proxies /api to the backend.
Use the Bug Report issue template. Include:
- What you did, what you expected, and what actually happened.
- Steps to reproduce (minimal, if possible).
- Browser/OS, Python version, and Node version.
- Relevant logs (backend stderr, browser console).
Use the Feature Request issue template. Describe the problem you want to solve, not just the solution. If you have a specific implementation in mind, include it — but the problem statement is what matters most.
- Fork the repository and create your branch from
main:git checkout -b feature/my-feature # or git checkout -b fix/issue-123 - Make your changes, following the Coding Standards below.
- Add or update tests for your changes.
- Verify everything passes (see Testing).
- Open a pull request against
main.
- One concern per PR. Split unrelated changes into separate PRs.
- Fill in the PR template. Describe what changed and why. Link the relevant issue.
- All CI checks must pass. Backend pytest, frontend Vitest/build, and Playwright E2E.
- Keep diffs focused. Avoid reformatting unrelated code or large whitespace-only changes.
- A maintainer will review. Address feedback by pushing new commits — don't force-push after a review has started.
- Once approved and CI is green, a maintainer will merge.
- Python 3.11+, managed via
uv. Never usepip installdirectly. - All models must inherit
CamelModelfrommodels/base.py— this ensures automatic snake_case → camelCase serialization. - Routers are thin. They validate input, call a service function, and return. No business logic in routers.
- All DB access lives in
services/. Routers never importget_client()directly. - Return
Nonefrom service functions for not-found cases; routers check and raiseHTTPException(404). - Use
logger = logging.getLogger(__name__)in every module that logs. - Background tasks must catch and log all exceptions — never let them propagate silently.
- Use
model_dump(exclude_unset=True)for partial updates — neverexclude_none. - No
snake_casekeys may leak into API responses. Runassert_camel_case_payloadin tests.
Error response shapes:
404:{"error": "not_found", "detail": "..."}500:{"error": "internal_server_error", "detail": "An unexpected error occurred."}409(duplicate):{"duplicates": [...], "paper": {...}}
- React 18, functional components only.
- Tailwind CSS utility classes in JSX
className— no per-component CSS files. - All API calls go through
src/services/api.js— components never callfetchdirectly. camelCasefor variables and functions;PascalCasefor components and files.UPPER_SNAKE_CASEfor module-level constants.- Follow the loading/error state pattern:
const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
- Wrap context mutations in
useCallback. - No TypeScript — plain
.js/.jsx.
cd backend
uv run --group dev pytest # run all tests
uv run --group dev pytest -x # stop on first failure
uv run --group dev pytest -k "dedup" # filter by nameNew backend code should have corresponding tests in backend/tests/. Mock at the router's import path (e.g., mocker.patch("routers.papers.paper_service.list_papers", ...)).
cd frontend
npm run test:runcd frontend
npx playwright install --with-deps chromium # first time only
npm run test:e2eRun the full suite and verify:
-
uv run --group dev pytest— all backend tests pass -
npm run test:run— all frontend unit tests pass -
npm run build— production build succeeds with no errors -
npm run test:e2e— Playwright smoke passes
Follow Conventional Commits:
<type>(<scope>): <short description>
[optional body]
[optional footer]
Common types: feat, fix, refactor, test, docs, chore.
Examples:
feat(import): add OpenAlex metadata resolution
fix(dedup): handle missing DOI in normalized-title tier
docs(readme): update migration list with 008–010
test(papers): add contract test for bulk PDF fetch endpoint
Keep the subject line under 72 characters. Use the body to explain why, not what.
Open a Discussion or file an issue — we're happy to help you get oriented.