# AGENTS.md — Gadget Code Monorepo ## Project Structure ``` gadget/ ├── packages/ │ ├── ai/ @gadget/ai — AI API abstraction (Ollama + OpenAI) │ ├── api/ @gadget/api — Shared TypeScript interfaces │ └── config/ @gadget/config — Env config loader ├── gadget-code/ Web service — Express 5, React 19, browser IDE │ └── frontend/ Frontend SPA — Vite 8 + React 19 (workspace member) └── gadget-drone/ Worker process — Agentic workflow loop ``` ## Rules for AI Code The ecosystem speaks **one** AI API language defined by `@gadget/ai`. All rules apply to every consumer: 1. **No direct SDK imports.** Never `import { Ollama } from "ollama"` or `import OpenAI from "openai"` outside `packages/ai/src/`. 2. **No `provider.sdk` checks outside the factory.** The factory in `packages/ai/src/index.ts` routes to the correct implementation. After `createAiApi()`, the caller holds an `AiApi` and never checks which SDK backs it. 3. **All AI interfaces live in `@gadget/ai`.** Do not re-declare `IAiProvider`, `IAiChatResponse`, etc. in consumer packages. Import them from `@gadget/ai`. 4. **Adding a new provider** means implementing `AiApi` in `packages/ai/src/` and registering it in the factory. Nothing else changes. ## Dev Commands ```bash pnpm install # Install all workspace packages pnpm -r build # Build all packages # Per-package build pnpm --filter @gadget/ai build pnpm --filter @gadget/api build pnpm --filter @gadget/config build pnpm --filter gadget-code build:backend # backend → dist/ + resolves @/* aliases pnpm --filter gadget-drone build pnpm --filter gadget-code-frontend build # frontend → dist/client/ # Run dev servers pnpm --filter gadget-code dev # Backend on https://localhost:3443 pnpm --filter gadget-drone dev # Drone worker pnpm --filter gadget-code-frontend dev # Frontend on https://localhost:5174 # Typecheck pnpm --filter @gadget/ai typecheck # Only package with a typecheck script ``` ## Key Conventions - All packages are **ES modules** (`"type": "module"`). - `@gadget/ai`, `@gadget/api`, `@gadget/config` use `moduleResolution: NodeNext` and emit to `dist/`. - gadget-drone uses `moduleResolution: NodeNext`. - gadget-code and gadget-code/frontend use `moduleResolution: bundler` with `@/*` path aliases → `src/*`. - **gadget-code build requires `tsc-alias`** to resolve `@/*` path aliases in output. - Dependency versions are pinned in `package.json` — no ranges. Use the workspace protocol (`workspace:*`) for internal package references. - pnpm version enforced at the workspace root (`packageManager` field). - **No lint scripts exist** in any package. ## TypeScript Strictness | Package | Strictness | | -------------- | ----------------------------------------------------------------------------------- | | `@gadget/ai` | `strict: true` | | `@gadget/api` | `strict: true` | | `@gadget/config` | `strict: true` | | `gadget-drone` | `strict: true` | | `gadget-code` | `strict: true`, `noUnusedLocals`, `noUnusedParameters`, `noUncheckedIndexedAccess` | | `gadget-code/frontend` | `strict: true` (included via workspace) | ## GadgetId All entity IDs use `GadgetId` (a string alias) from `@gadget/api`. Never use `ObjectId`. ```ts import { GadgetId } from "@gadget/api"; interface IMyEntity { _id: GadgetId; } ``` **Mongoose schema pattern:** ```ts _id: { type: String, default: () => nanoid() } ``` **Never add `unique: true` or `required: true` to `_id`**: MongoDB auto-creates a unique index on `_id` (causes duplicate index errors if `unique` is also set), and the default is assigned after validation (causes save failures if `required` is set). See `gadget-code/docs/gadget-id.md` for full documentation. ## Testing - `gadget-code`: Vitest unit tests (`pnpm test`). Vitest config excludes `tests/e2e/`. - E2E tests: Playwright targets `https://code-dev.g4dge7.com:5174` (requires running dev servers). - Seed test drones: `cd gadget-code && npx tsx scripts/seed-test-drones.ts` - `gadget-drone`: No tests configured. ## Adding Code When adding a new feature or service, determine its scope: - **Shared AI concern** (AI logging, config schema) → goes in `@gadget/ai` - **Shared types** → goes in `@gadget/api` - **Config loading** → goes in `@gadget/config` - **Drone-only** (Bull queue, workspace file operations) → goes in `gadget-drone` - **Web-only** (Express routes, Mongoose models, session management) → goes in `gadget-code` If code is needed by both consumer packages, it belongs in the appropriate shared package. Do not copy-paste shared logic across packages. ## Prerequisites - Node.js 22+ - pnpm (version enforced at root `packageManager` field) - MongoDB on `localhost:27017` - Redis on `localhost:6379` - SSL certificates in `gadget-code/ssl/` directory (for dev servers)