We welcome contributions! This guide covers everything you need to get started.
- Node.js 20+
- pnpm
- Docker (for local PostgreSQL)
# Fork on GitHub, then clone your fork
git clone git@github.com:YOUR_USERNAME/closedloop-ai.git
cd closedloop-ai
git remote add upstream git@github.com:closedloop-ai/closedloop-ai.git
# Install dependencies
pnpm install
# Start local database
docker compose up -d
# Set up environment variables
cp apps/app/.env.example apps/app/.env.local
cp apps/api/.env.example apps/api/.env.local
cp apps/web/.env.example apps/web/.env.local
# Run database migrations
cd packages/database && pnpm prisma migrate dev && cd ../..
# Start development
pnpm devSee docs/local_deployment.md for the full setup guide including service account configuration (Clerk, GitHub App, etc.).
# Type checking
pnpm typecheck
# Linting & formatting
pnpm lint
# Run tests
pnpm testAll contributions come through forks. External contributors do not have push access to the main repository.
- Fork the repository on GitHub
- Clone your fork and add the upstream remote (see Setup above)
- Create a feature branch from
main:git fetch upstream git checkout -b feat/my-change upstream/main
feat/*— New featuresfix/*— Bug fixesdocs/*— Documentation changesrefactor/*— Code restructuring
git fetch upstream
git rebase upstream/main- Push your branch to your fork (not the upstream repo)
- Open a PR from your fork's branch to
closedloop-ai/closedloop-ai:main - Include a description of what changed and why
- Address review feedback with additional commits (don't force-push during review)
- A maintainer will squash merge to
mainafter approval
closedloop-ai/
├── apps/ # Deployable applications
│ ├── app/ # Main application (port 3000)
│ ├── api/ # BFF API server (port 3002)
│ ├── web/ # Marketing website (port 3001)
│ ├── docs/ # Documentation (Mintlify)
│ ├── email/ # Email templates (React Email)
│ ├── storybook/ # Component library
│ └── studio/ # Prisma Studio
└── packages/ # Shared packages (@repo/*)
├── database/ # Prisma ORM + migrations
├── api/ # Shared API types
├── auth/ # Clerk authentication
├── design-system/ # Shadcn/ui components
├── ai/ # Anthropic AI integration
├── github/ # GitHub App integration
└── ... # 20+ shared packages
All database access goes through the BFF API — the frontend never imports @repo/database directly:
apps/app (frontend) → apps/api (routes → services) → @repo/database
- Frontend hooks (
apps/app/hooks/queries/) use TanStack Query withuseApiClient() - API routes (
apps/api/app/*/route.ts) handle auth and request parsing only — delegate to services - Services (
apps/api/app/*/service.ts) contain business logic and database operations - Shared types (
packages/api/src/types/) define contracts used by both frontend and backend
- Biome for linting and formatting (config in
biome.jsonc, extends ultracite) @repo/*imports before@/*path alias imports (Biome enforced)- Use
RegExp.exec(str)instead ofstr.match(regex) - Use
String#replaceAll()instead ofString#replace()with global regex - Use
globalThisinstead ofwindow - Keep function Cognitive Complexity under 15
- No nested ternary operators
- Prefer
Imagefromnext/imageover<img>elements - All Clerk client components need the mounted-state hydration guard pattern
- No JSX comments between
(and root JSX element
- Schema changes require
prisma migrate dev --name <descriptive_name> - Never use
prisma db pushfor changes going to production - Verify Prisma enum values against
schema.prisma— don't assume names
Read .gitmessage and follow its format for commit messages.
- Services: Unit tests for new service methods
- Parsers/Utilities: Unit tests required — PRs rejected without coverage
- Components: Update test fixtures when adding required props
- Do not assert on logging statements — test observable behavior instead
When responding to PR review comments, be concise and factual. State what was changed, not how insightful the reviewer was. Avoid phrases like "good catch" or "great point."