Monorepo Setup
This example shows how to configure Contextia for a monorepo with multiple packages that share a single knowledge layer. One .contextia/ directory at the repository root governs specs, decisions, and norms across all packages.
Project overview
Section titled “Project overview”The project is a SaaS platform with a shared API, a web frontend, a mobile backend-for-frontend (BFF), and a shared library of domain types and utilities.
platform/├── .contextia/│ ├── config.yaml│ └── system/│ ├── identity.md│ ├── specs/│ │ ├── SPEC-020.md # User registration (cross-package)│ │ ├── SPEC-021.md # Notification delivery│ │ └── SPEC-022.md # Shared validation rules│ ├── rationale/│ │ ├── DEC-020.md # Monorepo over polyrepo│ │ └── DEC-021.md # Shared types package│ └── norms/│ ├── NORM-MONO-001.md # Cross-package import rules│ ├── NORM-API-001.md # API response format (scoped to packages/api/)│ └── NORM-WEB-001.md # Component patterns (scoped to packages/web/)├── packages/│ ├── api/ # FastAPI backend│ │ ├── src/│ │ └── tests/│ ├── web/ # React frontend│ │ ├── src/│ │ └── tests/│ ├── mobile-bff/ # Mobile backend-for-frontend│ │ ├── src/│ │ └── tests/│ └── shared/ # Shared types and utilities│ ├── src/│ └── tests/├── package.json # Workspace root (or pyproject.toml, etc.)└── turbo.json # Build orchestrationConfiguration
Section titled “Configuration”The key configuration choice in a monorepo is listing all package source paths:
project: name: platform languages: - python - typescript source_paths: - packages/api/src/ - packages/web/src/ - packages/mobile-bff/src/ - packages/shared/src/
annotations: prefix: "@" comment_syntax: python: "#" typescript: "//"
check: ignore_patterns: - "packages/*/tests/**" - "packages/*/node_modules/**" - "packages/*/.next/**" - "packages/*/dist/**"Identity document
Section titled “Identity document”---project: platformversion: "1.0"---
## What is this
Platform is a SaaS product with four packages in a monorepo:- **api**: FastAPI backend serving REST endpoints.- **web**: React + TypeScript dashboard for browser clients.- **mobile-bff**: Lightweight backend-for-frontend optimized for mobile clients.- **shared**: TypeScript types, validation schemas, and utility functions used by all packages.
## Architecture
Monorepo managed with Turborepo. Each package has independent build and test pipelines.The shared package is a build dependency of api, web, and mobile-bff.
Cross-package communication:- web -> api: REST over HTTPS- mobile-bff -> api: Internal gRPC calls- All packages import types from shared
## Conventions
- Shared types are the single source of truth. Never duplicate a type from shared into a package.- API changes must update the shared types first, then consumers.- Each package has its own norms (component patterns for web, endpoint patterns for api).- Cross-package norms apply to all packages.Cross-package spec
Section titled “Cross-package spec”Some behaviors span multiple packages. For example, user registration involves the API (creates the account), the web app (registration form), and shared (validation schemas):
---id: SPEC-020title: User registrationdescription: End-to-end user registration spanning API, web frontend, and shared validation.status: approveddecisions: - DEC-021norms: - NORM-MONO-001paths: - packages/api/src/auth/registration.py - packages/web/src/pages/RegisterPage.tsx - packages/web/src/components/RegistrationForm.tsx - packages/shared/src/schemas/user.ts---
## Objective
Allow new users to register with email and password. Validation rules aredefined once in the shared package and enforced in both frontend and backend.
## Behaviors
WHEN a user submits the registration form with valid dataTHEN the web frontend validates locally using shared schemasAND sends a POST request to the APIAND the API validates again using the same shared schemasAND creates the user accountAND returns a success response with the user profile.
WHEN the email is already registeredTHEN the API returns HTTP 409 with error code "email_taken"AND the web frontend displays the error message.
WHEN the password does not meet complexity requirementsTHEN validation fails in both frontend and backend with the same error messageAND the shared schema defines the complexity rules.Shared types decision
Section titled “Shared types decision”---id: DEC-021title: Shared types package for cross-package consistencystatus: accepteddate: 2025-10-01specs: - SPEC-020 - SPEC-022---
## Context
Multiple packages need the same data types (User, Task, Notification).Duplicating types leads to drift -- the API accepts fields the frontenddoes not send, or validation rules diverge.
## Decision
Create a `shared` package containing:- TypeScript type definitions for all domain entities.- Zod schemas for validation (used by web and mobile-bff).- Python dataclasses generated from the TypeScript types (for api).
## Consequences
- Single source of truth for data shapes.- Shared package becomes a build dependency for all others.- Changes to shared require rebuilding all dependent packages.- Need a generation step to keep Python types in sync with TypeScript.Path-scoped norms
Section titled “Path-scoped norms”Norms can apply to specific packages using the paths field:
API-specific norm
Section titled “API-specific norm”---id: NORM-API-001title: API response formatstatus: activepaths: - packages/api/src/**---
## Rules
1. All endpoints MUST return the envelope format: `{ data, error, meta }`.2. Error responses MUST include an error code (snake_case string) and a human-readable message.3. List endpoints MUST support pagination with `offset` and `limit` query parameters.4. All timestamps MUST be ISO 8601 in UTC.Web-specific norm
Section titled “Web-specific norm”---id: NORM-WEB-001title: Component patternsstatus: activepaths: - packages/web/src/**---
## Rules
1. Components MUST be functional with hooks. No class components.2. Each component directory contains: Component.tsx, Component.module.css, index.ts.3. Components MUST NOT import directly from other packages. Use the shared package.4. Page-level components live in src/pages/. Reusable components live in src/components/.5. All user-facing text MUST use the i18n translation function, never hardcoded strings.Cross-package norm
Section titled “Cross-package norm”---id: NORM-MONO-001title: Cross-package import rulesstatus: activepaths: - packages/**---
## Rules
1. Packages MUST only import from `shared` for cross-package types. No direct imports between api, web, and mobile-bff.2. The `shared` package MUST NOT import from any other package.3. All cross-package types MUST be defined in `packages/shared/src/`.4. Runtime dependencies between packages are forbidden. Use API calls for cross-service communication.Annotated source code
Section titled “Annotated source code”Shared validation schema
Section titled “Shared validation schema”// @spec SPEC-020// @spec SPEC-022import { z } from 'zod';
export const registrationSchema = z.object({ email: z.string().email('Invalid email format'), password: z .string() .min(8, 'Password must be at least 8 characters') .regex(/[A-Z]/, 'Password must contain an uppercase letter') .regex(/[0-9]/, 'Password must contain a number'), name: z.string().min(1, 'Name is required').max(100),});
export type RegistrationInput = z.infer<typeof registrationSchema>;API registration endpoint
Section titled “API registration endpoint”# @spec SPEC-020# @norm NORM-API-001# @norm NORM-MONO-001from shared.schemas import RegistrationInput, validate_registrationfrom src.auth.service import create_user
async def register(data: RegistrationInput) -> dict: """Create a new user account after shared-schema validation.""" errors = validate_registration(data) if errors: return {"data": None, "error": {"code": "validation_failed", "details": errors}, "meta": {}}
user = await create_user(data) return {"data": user.to_dict(), "error": None, "meta": {}}Web registration form
Section titled “Web registration form”// @spec SPEC-020// @norm NORM-WEB-001// @norm NORM-MONO-001import { registrationSchema, type RegistrationInput } from '@platform/shared';import { useForm } from 'react-hook-form';import { zodResolver } from '@hookform/resolvers/zod';
export default function RegistrationForm() { const { register, handleSubmit, formState: { errors } } = useForm<RegistrationInput>({ resolver: zodResolver(registrationSchema), });
const onSubmit = async (data: RegistrationInput) => { const response = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); // handle response... };
return ( <form onSubmit={handleSubmit(onSubmit)}> {/* form fields */} </form> );}Context assembly across packages
Section titled “Context assembly across packages”When an agent works on a task that involves registration:
contextia context TASK-030The context includes artifacts from all relevant packages, plus the cross-package norms. The agent sees the full picture — shared schema, API endpoint, and web form — and understands the constraints that span all three.
Next steps
Section titled “Next steps”- See the Python REST API example for a single-package setup.
- Read the Directory Structure reference for the full
.contextia/layout. - Check the Configuration Reference for all monorepo-relevant settings.