Contributing
Everything you need to set up a local development environment and contribute to ToolPilot.
Prerequisites
- Node.js 22 LTS — required runtime
- pnpm 9+ — workspace-aware package manager
- Docker — runs Memgraph, Qdrant, PostgreSQL, and Redis via Compose
Development Setup
Clone and start the full stack in under a minute:
git clone https://github.com/toolpilot/toolpilot
cd toolpilot
pnpm install
pnpm db:up # Start Memgraph, Qdrant, PostgreSQL, Redis
pnpm db:seed # Seed initial tool data
pnpm dev # Start all dev serversFirst-time setup
pnpm db:up pulls Docker images on the first run, which may take a few minutes depending on your connection. Subsequent starts are near-instant.Project Structure
Monorepo layout managed by pnpm workspaces and Turborepo:
toolpilot/
├── apps/
│ ├── mcp-server/ # MCP protocol server (primary product)
│ ├── web/ # Next.js admin dashboard
│ ├── public/ # Next.js public site & docs
│ └── indexer/ # GitHub indexer + workers
├── packages/
│ ├── core/ # Shared types, Zod schemas
│ ├── graph/ # Memgraph client + Cypher queries
│ ├── vector/ # Qdrant + Nomic embeddings
│ ├── search/ # 4-stage search pipeline
│ ├── db/ # PostgreSQL (Prisma)
│ ├── queue/ # Redis Streams (ioredis)
│ └── config/ # Validated env config (Zod)
├── docker-compose.yml # Infrastructure services
├── turbo.json # Turborepo pipeline config
├── pnpm-workspace.yaml # Workspace definitions
└── biome.json # Linter / formatter configCoding Standards
TypeScript strict mode
Strict compiler options enabled. No any type except in generated code (e.g. Prisma).
Named exports only
Every module uses named exports. Default exports are not allowed (except Next.js pages).
Zod validation
All external input (API requests, env vars, MCP parameters) is validated with Zod schemas.
Result pattern
Domain logic returns { ok: true, data } | { ok: false, error } instead of throwing exceptions.
Repository interfaces
All database access goes through repository interfaces, enabling in-memory fakes for testing.
Structured logging
pino with JSON output. No console.log in production code.
Async error handling
All async functions use try/catch. No unhandled promise rejections.
Result Pattern
Domain functions return typed results instead of throwing:
1// ✅ Domain functions return Result types
2type Result<T> =
3 | { ok: true; data: T }
4 | { ok: false; error: string };
5
6function findTool(name: string): Result<Tool> {
7 const tool = toolRepository.findByName(name);
8 if (!tool) {
9 return { ok: false, error: `Tool "${name}" not found` };
10 }
11 return { ok: true, data: tool };
12}
Repository Pattern
All database access is abstracted behind interfaces:
1// ✅ Repository interface for testable DB access
2interface ToolRepository {
3 findByName(name: string): Promise<Tool | null>;
4 findRelated(toolId: string, limit: number): Promise<Tool[]>;
5 upsert(tool: Tool): Promise<void>;
6}
7
8// Production: backed by Memgraph
9// Tests: backed by in-memory Map
File Naming
All files and directories use kebab-case. Recognised suffixes:
Testing
- Vitest — unit and integration tests (
pnpm testorpnpm test:unit) - Playwright — end-to-end browser tests (
pnpm test:e2e) - Tests are colocated with source files using the
.test.tssuffix
Commit Conventions
ToolPilot follows Conventional Commits. Format: type(scope): description
# Format: type(scope): description
feat(search): add language filter to Stage 2
fix(graph): handle null edge weights in reranking
refactor(mcp): extract validation into shared schema
test(vector): add embedding dimension tests
docs(architecture): add data flow diagrams
chore(infra): upgrade Qdrant to 1.12
ci(indexer): add schedule trigger for nightly runsScopes
graph, search, mcp, indexer, web, admin, infra, core.