# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Keeping This File Up to Date Update this file whenever a lesson is learned during development. Specifically, update CLAUDE.md when: - A non-obvious architectural decision is made or discovered - A gotcha, footgun, or surprising behavior is encountered (and the fix/workaround) - A new command, workflow, or tool is added to the project - A convention is established that isn't obvious from reading the code - An integration detail is clarified (e.g., how the IPC protocol actually behaves, edge cases in the agent tool call cycle) Do **not** add things already derivable from reading the code, generic best practices, or ephemeral task notes — only durable, reusable knowledge. ## Repository Layout This is a **monorepo with git submodules**. Each submodule is an independent repo with its own `.claude/CLAUDE.md` for detailed guidance. | Directory | What | Submodule | |-----------|------|-----------| | **`adiuvAI/`** | Electron desktop app (TypeScript/React) | `git.muticolturano.com/adiuvAI/adiuvAI` | | **`api/`** | FastAPI backend (Python) | `git.muticolturano.com/adiuvAI/api` | | **`website/`** | Landing page (single `index.html`) | `git.muticolturano.com/adiuvAI/website` | | **`docs/`** | Planning docs & working memory (not a submodule) | -- | After cloning, run `git submodule update --init --recursive` to populate submodule contents. --- ## adiuvAI (Electron App) > **Detailed docs**: `adiuvAI/.claude/CLAUDE.md` covers commands, architecture, AI subsystem, design context, and conventions in depth. ### Commands ```bash cd adiuvAI npm run start # Start dev server (Electron + Vite) npm run lint # ESLint npm run knip # Dead code analysis npm run make # Build installers (Windows/Linux/macOS) npm run package # Package without creating installers npx drizzle-kit generate # Generate migration from schema changes npx drizzle-kit push # Push schema directly (dev only) ``` No test suite currently. ### Architecture ``` Renderer (React 19 + TanStack Router) ↓ custom ipcLink (NOT electron-trpc — incompatible with tRPC v11) Preload (contextBridge: window.electronTRPC + window.electronAI) ↓ IPC channels Main Process (Node.js) ├── tRPC router (all CRUD + AI procedures) ├── SQLite (better-sqlite3 + Drizzle ORM, WAL mode) ├── LanceDB (vector embeddings, 1536-dim text-embedding-3-small) └── LangGraph orchestrator (3 specialist agents, pluggable LLM providers) ``` **This is a local-first app.** All user data (tasks, notes, projects) lives in local SQLite. The AI system (LangGraph + LangChain) runs entirely in the Electron main process with pluggable providers (OpenAI, Anthropic, GitHub Copilot). **IPC channels**: - `'trpc'` — bidirectional tRPC request/response (all CRUD) - `'ai:stream'` — one-way token streaming from main → renderer - `'ai:action'` — AI side-effects (e.g., task auto-created by agent) **Key source paths**: - `src/main/ipc.ts` — Custom tRPC↔IPC bridge - `src/main/router/index.ts` — All tRPC routers (~600 LOC) - `src/main/ai/orchestrator.ts` — LangGraph intent routing + 3 agents (~991 LOC) - `src/main/db/schema.ts` — 6 tables (clients, projects, tasks, checkpoints, notes, taskComments) - `src/renderer/routes/` — File-based routing (TanStack Router auto-generates `routeTree.gen.ts`) - `src/renderer/components/ui/` — shadcn/ui primitives (new-york theme, neutral colors) **Non-obvious details**: - `electron-trpc` is NOT used — custom IPC bridge in `ipc.ts` + `ipcLink.ts` because electron-trpc bundles tRPC v10 internals - Vite configs use `.mts` extension to avoid ESM/CJS conflicts with electron-forge - `forge.config.ts` has complex cross-compilation hooks (downloads platform-specific native binaries for better-sqlite3 and LanceDB) - DB has no foreign key constraints — cascade deletes are implemented in tRPC procedures - Timestamps are milliseconds (JavaScript `Date.getTime()`), not ISO strings - Notes auto-embed to LanceDB on create/update (fire-and-forget, errors swallowed) --- ## api (FastAPI Backend) ### Commands ```bash cd api # Development uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 # Production gunicorn app.main:app -k uvicorn.workers.UvicornWorker -w 4 --timeout 120 # Database migrations alembic upgrade head # Testing pytest # all tests pytest -v # verbose pytest tests/test_agents.py # single file pytest tests/test_agents.py -k test_name # single test # Linting/formatting ruff check . ruff format . # Docker (full stack: app + postgres + minio + qdrant) docker compose up --build ``` ### Architecture ``` FastAPI app (app/main.py) ├── Middleware: TierRateLimiter → Sanitizer → CORS ├── HTTP Routes (app/api/routes/) │ ├── auth.py — register, login, token refresh (bcrypt + HS256 JWT) │ ├── chat.py — POST /chat, WS /chat/stream │ ├── plans.py — execution plan playbooks │ ├── storage.py — E2E-encrypted cloud storage (S3) │ ├── backup.py — encrypted backup upload/download │ ├── vectors.py — encrypted vector upsert/search (Pinecone/Qdrant) │ ├── plugins.py — plugin marketplace (Power+ tier) │ └── billing.py — Stripe subscriptions ├── Agent System (app/agents/) │ ├── task_agent.py — 8 tools │ ├── project_agent.py — 6 tools │ ├── checkpoint_agent.py — 4 tools │ └── note_agent.py — 5 tools ├── Orchestration (app/core/) │ ├── orchestrator.py — intent classification + agent routing │ ├── agent_registry.py — decorator-based agent registry │ ├── execution_plan.py — server-side prompt templates + plan builder │ ├── llm.py — LiteLLM factory (100+ providers) │ └── memory_middleware.py ├── Billing (app/billing/) │ ├── tier_manager.py — feature matrix (Free/Pro/Power/Team) │ └── stripe_service.py — Stripe checkout + webhooks ├── Storage (app/storage/) — S3 blob store, vector store, encryption └── Marketplace (app/marketplace/) — plugin catalog, review, revenue sharing ``` **LLM routing**: `gpt-4o-mini` classifies intent → routes to domain agent → agent uses `gpt-4o` with tools → tool calls describe client-side operations (JSON) → Electron executes locally and returns results. **Zero-trust data model**: The backend never stores or decrypts user content. PostgreSQL holds only auth, billing, plugin metadata, and storage record pointers. All user data is E2E-encrypted before leaving the Electron client. **Key config**: `app/config/settings.py` — all env vars via Pydantic Settings. Copy `.env.example` to `.env` for local dev. Stripe and S3 gracefully stub when keys aren't configured. **Database**: PostgreSQL with async SQLAlchemy 2.0 + asyncpg. 9 ORM models in `app/models.py`. Alembic migrations in `alembic/versions/`. **Testing**: pytest + pytest-asyncio. Fixtures in `tests/conftest.py` create in-memory SQLite + moto-mocked S3. Test users seeded per tier (free/pro/power/team). ### Non-obvious details - **Tier from DB, not JWT**: `get_current_user` decodes JWT but fetches authoritative tier from `subscriptions` table — tier changes take effect immediately without re-login - **Refresh tokens hashed**: Plaintext returned to client, stored as SHA-256 in DB — server can never retrieve the plaintext (intentional) - **WebSocket auth via query param**: `?token=` instead of Bearer header (WebSocket handshake limitation) - **Prompt IP protection**: `PromptTemplateRegistry` keeps prompts server-side; clients receive opaque `template_id`. `SanitizerMiddleware` strips leaked fragments from responses - **Agents don't execute operations**: Tools return JSON describing client-side ops — the Electron client executes against local SQLite - **Alembic async/sync split**: App uses `postgresql+asyncpg`, Alembic CLI needs `postgresql+psycopg2` — `env.py` handles the URL conversion - **Tool loop cap**: Agent `_tool_loop` stops after 5 iterations to prevent infinite loops - **Route order matters**: `/backup/history` must be declared before `/backup/{backup_id}` to avoid path param shadowing - **CORS includes `app://`**: Electron uses custom `app://` protocol, not http/https - **Vector search on encrypted data is not semantic**: Backend derives deterministic 32-dim floats from blob SHA-256 for storage/search — a known trade-off ### Tier System | Feature | Free | Pro | Power | Team | |---------|------|-----|-------|------| | Rate limit | 20/min | 60/min | 120/min | 200/min | | Agents | 3 | unlimited | unlimited | unlimited | | Cloud storage | 0 GB | 5 GB | 25 GB | unlimited | | Plugin marketplace | no | no | yes | yes | Enforced in `app/api/middleware/rate_limit.py` (sliding window) and `app/billing/tier_manager.py` (feature checks + quota enforcement). --- ## Cross-Project Integration The Electron app and FastAPI backend communicate via **WebSocket** (`/chat/stream`): 1. Electron connects with `?token=` query param 2. Client sends `ChatRequest` JSON frame 3. Server streams text chunks, then a final frame: `{"done": true, "response": "...", "actions": []}` 4. Server sends `tool_call` frames → Electron executes against local SQLite → returns `tool_result` 5. Server pings every 30 seconds to keep connection alive The Electron app also has a **fully local AI path** (LangGraph orchestrator in main process) that doesn't require the backend — this is the primary path for desktop use. --- ## MCP Servers - **Langfuse Docs** (`https://langfuse.com/api/mcp`) — configured at workspace level for prompt management documentation - **shadcn** (`npx shadcn@latest mcp`) — configured in `adiuvAI/` for UI component generation