From a4a07df7fa8921a2290a3f2c5a71d96f816b68b9 Mon Sep 17 00:00:00 2001 From: Roberto Musso Date: Wed, 8 Apr 2026 23:22:02 +0200 Subject: [PATCH] update CLAUDE.md and clean up MCP config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix directory names (adiuva/ → adiuvAI/, adiuva-api/ → api/) - Document git submodule structure and clone instructions - Correct Electron architecture (local-first LangGraph, not backend-dependent) - Add non-obvious gotchas for both projects - Mark microservices migration as planned but not yet started - Remove duplicate langfuse-docs MCP definition from settings.json - Simplify settings.local.json (enableAllProjectMcpServers replaces explicit list) Co-Authored-By: Claude Sonnet 4.6 --- .claude/CLAUDE.md | 210 ++++++++++++++++++++---------------- .claude/settings.json | 10 +- .claude/settings.local.json | 3 - 3 files changed, 116 insertions(+), 107 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 3842ea9..e55dfeb 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -10,68 +10,90 @@ Update this file whenever a lesson is learned during development. Specifically, - 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 WebSocket protocol actually behaves, edge cases in the agent tool call cycle) +- 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 repo contains two independent projects: +This is a **monorepo with git submodules**. Each submodule is an independent repo with its own `.claude/CLAUDE.md` for detailed guidance. -- **`adiuva/`** — Electron desktop app (TypeScript/React) -- **`adiuva-api/`** — FastAPI backend (Python) +| 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. --- -## adiuva (Electron App) +## 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) ``` -Database schema changes require running Drizzle Kit — check `package.json` for db commands. +No test suite currently. ### Architecture ``` Renderer (React 19 + TanStack Router) - ↓ tRPC over contextBridge -Main Process (Electron) - ├── SQLite (better-sqlite3 + Drizzle ORM) — local app data - ├── LanceDB — local vector embeddings - └── WebSocket client → adiuva-api backend + ↓ 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) ``` -**IPC model**: The renderer calls tRPC procedures defined in `src/main/router/`. The preload script (`src/preload/`) bridges them with `contextIsolation: true`. +**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). -**Backend integration**: The Electron main process connects to the FastAPI backend via WebSocket. The backend sends tool calls (e.g., `insert`, `vector_search`) which the main process executes against local SQLite via Drizzle and returns results. All AI intelligence lives on the backend — the app is a smart terminal. +**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 directories**: -- `src/main/agents/` — Agent scheduler -- `src/main/ai/` — Orchestrator, token management -- `src/main/db/` — SQLite schema (Drizzle) + LanceDB -- `src/main/router/` — tRPC router (all IPC procedures) -- `src/renderer/components/` — UI components (tasks, notes, projects, timeline, auth) -- `src/renderer/routes/` — TanStack Router pages -- `src/shared/` — Zod schemas shared between main/renderer (WebSocket frame types, casing utils) +**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) -**Path aliases** (tsconfig): `@/*` → `src/renderer/`, `@shared/*` → `src/shared/` - -**WebSocket frame types** are defined in `src/shared/api-types.ts` using Zod. Client sends: `chat_request`, `floating_request`, `tool_result`. Server sends: `text_chunk`, `tool_call`, `final`, `ping`. +**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) --- -## adiuva-api (FastAPI Backend) +## api (FastAPI Backend) ### Commands ```bash +cd api + # Development uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 @@ -82,15 +104,16 @@ gunicorn app.main:app -k uvicorn.workers.UvicornWorker -w 4 --timeout 120 alembic upgrade head # Testing -pytest -pytest -v -pytest tests/test_agents.py # single test file +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) +# Docker (full stack: app + postgres + minio + qdrant) docker compose up --build ``` @@ -98,88 +121,85 @@ docker compose up --build ``` FastAPI app (app/main.py) -├── Middleware: RateLimiter → Sanitizer → CORS +├── Middleware: TierRateLimiter → Sanitizer → CORS ├── HTTP Routes (app/api/routes/) -│ ├── auth.py — register, login, token refresh -│ ├── chat.py — POST /chat, POST /chat/embed, WS /chat/stream +│ ├── 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 -│ ├── timeline_agent.py — 4 tools -│ └── note_agent.py — 5 tools -└── Orchestration (app/core/) - ├── agent_registry.py - ├── agent_runner.py - ├── llm.py — LiteLLM factory (100+ providers) - └── memory_middleware.py +│ ├── 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 incoming intent → routes to appropriate domain agent → agent uses GPT-4o with its tool set → sends tool calls back to Electron client for local execution. +**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 decrypts user data. PostgreSQL stores only auth, billing, and metadata. All user content stays local on the Electron client. +**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. -**Tier system**: Free / Pro / Power / Team — enforced in `app/api/middleware/rate_limit.py` (20–200 req/min sliding window) and `app/billing/tier_manager.py`. +**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. -**Key config**: `app/config/settings.py` — all env vars via Pydantic Settings. Copy `.env.example` to `.env` for local dev. +**Database**: PostgreSQL with async SQLAlchemy 2.0 + asyncpg. 9 ORM models in `app/models.py`. Alembic migrations in `alembic/versions/`. -**Database**: PostgreSQL with async SQLAlchemy 2.0 + asyncpg. Migrations in `alembic/versions/`. Models in `app/models.py`, Pydantic schemas in `app/schemas.py`. +**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). -**Testing**: pytest with pytest-asyncio. Fixtures in `tests/conftest.py`. Use in-memory SQLite for DB tests. +### 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). --- -## Microservices Migration (In Progress) +## Cross-Project Integration -The monolith (`adiuva-api/app/`) is being split into independent services under `adiuva-api/services/`. Architectural decisions are tracked in repo memory (`/memories/repo/microservices-architecture.md`). +The Electron app and FastAPI backend communicate via **WebSocket** (`/chat/stream`): -### Target Services (MVP) +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 -| Service | Owns | Scaling | -|---------|------|---------| -| **Auth** | JWT RS256 issuance, users, refresh_tokens, subscriptions | Stateless | -| **WS Gateway** | WebSocket connections, Redis frame routing, device registry | Sticky (user_id) | -| **Chat** | deep_agent, memory, domain agents (task/note/project/timeline), LLM | Stateless | -| **Batch Agent** | agent_runner, journey builder, filesystem_agent, integrations (+ Langfuse tracing TODO) | Stateless | -| **Billing** | Stripe, tier_manager | Stateless | +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. -**API Gateway**: Traefik with ForwardAuth → Auth `/verify`. Injects `X-User-Id`, `X-User-Email`, `X-User-Tier` headers. Downstream services trust these headers. +--- -### Monorepo Structure +## MCP Servers -``` -adiuva-api/ -├── shared/ ← SQLAlchemy models, Pydantic schemas, config, redis utils -├── services/ -│ ├── auth/ -│ ├── ws-gateway/ -│ ├── chat/ -│ ├── batch-agent/ -│ └── billing/ -├── alembic/ ← Centralized migrations (shared DB) -├── docker-compose.yml -└── traefik/ -``` - -### Key Conventions - -- **shared/ module**: Imported by all services. Contains models, schemas, config, DB session factory, Redis client. Changes here affect all services — be careful. -- **Redis is the glue**: WS Gateway ↔ Chat/Batch communication is entirely via Redis pub/sub and lists. See `/memories/repo/microservices-architecture.md` for channel naming. -- **No JWT validation in downstream services**: Only Auth Service has the private key. Other services receive pre-validated identity via Traefik headers. -- **Tool call round-trip**: Chat/Batch → publish `tool_call` to `ws:out:{user_id}` → WS Gateway forwards to Electron → Electron replies `tool_result` → WS Gateway LPUSH to `tool:result:{call_id}` → Chat/Batch BRPOP with 30s timeout. -- **Migration strategy**: Strangler fig. Extract one service at a time. The monolith `app/` continues to work until all services are extracted. Don't delete monolith code until the service replacement is tested. -- **Storage & Plugin services**: Removed from codebase. Will be re-evaluated in future feature planning. - -### Commands (Microservices) - -```bash -# Full stack -docker compose up --build - -# Single service dev (example: chat) -docker compose up redis postgres auth -cd services/chat && uvicorn app.main:app --reload --port 8002 - -# Migrations (still centralized) -alembic upgrade head -``` +- **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 diff --git a/.claude/settings.json b/.claude/settings.json index 9d38cdd..9e26dfe 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,9 +1 @@ -{ - "mcpServers": { - "langfuse-docs": { - "transportType": "http", - "url": "https://langfuse.com/api/mcp", - "verifySsl": true - } - } -} \ No newline at end of file +{} \ No newline at end of file diff --git a/.claude/settings.local.json b/.claude/settings.local.json index cfa4940..13357b6 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,6 +1,3 @@ { - "enabledMcpjsonServers": [ - "langfuse-docs" - ], "enableAllProjectMcpServers": true }