9 KiB
ANTIGRAVITY.md
This file provides guidance to Antigravity (the Gemini-based coding assistant) when working with code in this repository.
Project Overview
MeshiTrack is a self-hosted medicine & nutrition management platform. It is a TypeScript monorepo with three packages: packages/api (Fastify backend), packages/web (Next.js frontend), and packages/shared (types + Zod schemas used by both). Medicine tracking is implemented first (Phases 1-4), followed by food tracking (Phases 5-9).
Commands
Root (all packages via Turborepo)
npm run dev # Start all services in dev mode
npm run build # Build all packages (shared → api/web)
npm run test # Run all tests
npm run test:cov # Run all tests with coverage
npm run lint # Lint all packages
npm run lint-fix # Auto-fix lint issues
npm run typecheck # Type-check all packages
npm run clean # Remove build artifacts
npm run seed # Seed the database (delegates to packages/api)
Single package
npm run test -w packages/api # Run API tests
npm run test:cov -w packages/api # API tests with coverage
npm run test -- --watch -w packages/api # Watch mode
npm run dev -w packages/api # API dev server only
API package (packages/api)
npm run dev # tsx watch src/main.ts
npm run build # tsc
npm run seed # tsx src/scripts/seed.ts
Docker
docker compose -f docker/docker-compose.yml up -d # Start all services
docker compose -f docker/docker-compose.yml down # Stop all services
Implementation Workflow
Every feature or phase implementation MUST start with a rigorous planning and design phase:
-
Mandatory Planning (Grilling): Before starting any feature or bug fix, you MUST invoke the
grill-with-docsskill.- Run the skill using:
view_fileon.agents/skills/grill-with-docs/SKILL.mdand follow its instructions. - This session will stress-test your plan against the existing domain model, terminology, and documentation (
CONTEXT.md, ADRs). - Decisions must be crystallized and documentation (glossary/ADRs) updated before moving to implementation.
- Run the skill using:
-
Vertical Slice Implementation: Follow this approach for the actual build:
-
Database Layer: Create the Mongoose schema and Repository in
packages/api. All reads must use.lean().exec(). -
Service Layer: Implement business logic in the Service class, using Awilix for constructor injection. Add unit tests.
-
Route Layer: Create the Fastify route plugin and register it. Add route tests (using
app.inject). -
Web API Client: Implement the frontend service in
packages/web/src/services/. Add unit tests. -
Web UI: Create the Next.js pages and components. Use Server Components by default. Add component tests (React Testing Library).
Verification Gate (Mandatory): Before considering a task complete, run:
npm run buildnpm run test:cov(must meet 100/100/100/90 for API/Shared and 90/85/75/85 for Web)npm run lint
Architecture
Monorepo Structure
packages/shared— Single source of truth for all domain types, enums, and Zod v4 schemas. Consumed by bothapiandweb. Must be pure TypeScript with no Node.js, browser, or framework dependencies.packages/api— Fastify 5 backend. ESM-only, TypeScript strict. Uses Awilix for DI, Mongoose 9 for MongoDB, jose 6 for JWT verification.packages/web— Next.js 16 (React 19) frontend. App Router, Tailwind CSS 4.
API Layer Architecture (Fastify + Awilix)
The API follows a routes → services → repositories pattern with Awilix constructor-injection DI:
- Each domain feature lives in
src/modules/<feature>/with files:*.routes.ts,*.service.ts,*.repository.ts - Route plugins use
fastify-plugin(fp()) to export routes and register Awilix dependencies - Services receive dependencies via destructured constructor:
constructor({ productsRepository }: { productsRepository: ProductsRepository }) - Resolve services per-request via
request.diScope.resolve<T>('serviceName') - All Mongoose read queries must use
.lean().exec()
Plugin registration order in main.ts: security → compression → swagger → DI container → database → auth → household guard → route modules.
Domain Modules
Medicine domain (Phases 1-4): medicines/, medicine-products/, cabinet/, regimens/, organizer/, medicine-prices/, purchases/, refills/
Food domain (Phases 5-9): products/, recipes/, pantry/, meal-plans/, grocery/
Shared: health/, users/, households/, stores/, llm/
Multi-tenancy
Every domain document is scoped to a householdId. A Fastify preHandler hook validates the householdId from the URI against the user's householdIds[] JWT claim. Every data query must filter by householdId.
Routes can opt out with config: { public: true } (skips auth) or config: { skipHousehold: true } (skips household validation).
Auth
Keycloak is the OIDC provider. The API verifies JWTs via jose. The custom Keycloak protocol mapper injects householdIds[] into the JWT claims.
Shared Package Rules
- All domain types and Zod schemas live here — never duplicate types across packages
- Import from
'zod/v4'(not'zod') - Use
z.enum()for enums,z.email()/z.url()as top-level calls - Every directory has a barrel
index.ts - Use
import typefor type-only imports
Pagination
All list endpoints use cursor-based pagination. Never use skip() on MongoDB queries. Response shape:
{ data: T[], pagination: { cursor: string | null, hasMore: boolean, total?: number } }
Error Handling
Services throw AppError subclasses (NotFoundError, ConflictError, ForbiddenError, etc.). The global Fastify error handler maps them to the standard ApiError response shape (statusCode, error, message, timestamp, path).
Key Rules
- No
anytypes — useunknown+ Zod validation at boundaries - ESM everywhere —
"type": "module",.jsextensions on all imports,import typefor type-only - Cursor-based pagination only — never
skip()for large collections - Zod v4 — import from
'zod/v4' .lean().exec()on all Mongoose read querieshouseholdIdfilter on every domain query — this is the multi-tenancy boundary- No emojis — never use emoji characters in source code, UI text, console output, or documentation
npxis banned — never runnpxfor any reason. Usenpm run <script>for all test, lint, build, and tool invocations. No exceptions.- Never pipe or redirect
npm runcommands — runnpm run <script>exactly as written; never append2>&1,|,Select-Object,Select-String, or any other shell constructs to it. - Never use shell commands to read files — always use
view_filetool. Commands likeGet-Content,cat,head,tailwill be denied. - Never use shell commands to search — always use
grep_searchorfile_search. Commands likeSelect-String,grep,rg,findwill be denied. - Never call tools in parallel — always wait for one tool call to complete before calling the next. This applies to all tools: file reads, searches, and terminal commands.
- Every implementation task must end with
npm run build,npm run test:cov, andnpm run lintall passing. Always usetest:cov(nottest) to enforce coverage thresholds. If coverage fails, write additional tests before considering the task done. - No
.jsextensions on@/imports inpackages/web— Next.js resolves TypeScript files directly;.jsextensions on@/path-alias imports break Turbopack and webpack. Only use.jsextensions inpackages/apiandpackages/shared(Node ESM).
Testing
- Unit tests: Vitest, co-located with source files as
*.routes.test.ts/*.service.test.ts - Integration tests: Vitest +
mongodb-memory-server(*.integration.test.ts) - Component tests: React Testing Library (
__tests__/*.test.tsx) - E2E: Playwright (root-level
e2e/) - Use
ClassName.namefordescribelabels (not string literals) - Coverage targets:
packages/api&packages/shared: 100% lines/functions/statements, 90% branchespackages/web: 90% lines, 85% functions, 75% branches, 85% statements
- Mark untestable lines with
/* v8 ignore */
Documentation
Before writing code, consult the relevant docs:
docs/instructions/— coding conventions, Fastify patterns, Next.js patterns, MongoDB, Zod/TypeScript, testing, Docker, Keycloak, Turborepodocs/phases/— per-phase specs with schemas, endpoints, and business logicdocs/architecture.md— ADRs explaining key technology choicesdocs/cross-cutting.md— API versioning, pagination, security, audit trail
Git Conventions
Branches: feature/MESH-001-description, fix/MESH-042-description, chore/description
Commits follow Conventional Commits: feat(products): add barcode lookup, fix(pantry): correct freshness calc