6.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
# Development
source ~/.nvm/nvm.sh && npm start # Start Electron app with hot-reload
# Build & Package
source ~/.nvm/nvm.sh && npm run make # Build distributable packages
source ~/.nvm/nvm.sh && npm run package # Package without making installers
# Lint
source ~/.nvm/nvm.sh && npm run lint # ESLint over .ts/.tsx files
# Database migrations (Drizzle)
source ~/.nvm/nvm.sh && npx drizzle-kit generate # Generate migration from schema changes
source ~/.nvm/nvm.sh && npx drizzle-kit push # Push schema directly (dev only)
There is no test suite currently.
Architecture Overview
Adiuva is a local-first Electron desktop app. The three Electron processes communicate via a custom tRPC↔IPC bridge (the public electron-trpc package is incompatible with tRPC v11, so a custom implementation is used).
Process Boundaries
Renderer (React) ──ipcLink──► Preload (contextBridge) ──IPC──► Main (tRPC router + SQLite)
-
Main process (
src/main/) — Node.js, owns the database and all business logicindex.ts— Window creation, app lifecycleipc.ts— Custom handler that bridgesipcMainto tRPC proceduresrouter/index.ts— All tRPC routers (clients, projects, tasks, checkpoints, notes, settings, ai)db/index.ts— Drizzle + better-sqlite3, WAL mode, singletongetDb()db/schema.ts— All table definitions (clients, projects, tasks, checkpoints, notes)store.ts— electron-store for persistent UI settings (e.g.,sidebarCollapsed)
-
Preload (
src/preload/trpc.ts) — Exposeswindow.electronTRPCwithsendMessage()/onMessage() -
Renderer (
src/renderer/) — React 19, never accesses Node APIs directlylib/ipcLink.ts— Custom TRPCLink that routes calls throughwindow.electronTRPClib/trpc.ts—createTRPCReact<AppRouter>()typed clientindex.tsx— QueryClient + tRPC + Router providers- All data access is through
trpc.*.*useQuery()/trpc.*.*.useMutation()
Routing
File-based routing via TanStack Router. Add a file to src/renderer/routes/ and the route tree (src/renderer/routeTree.gen.ts) is auto-regenerated by the Vite plugin on next npm start. Routes:
__root.tsx— Root layout wrapping everything inAppShellindex.tsx,tasks.tsx,timeline.tsx,projects.tsx
Database
Schema lives in src/main/db/schema.ts. Migrations are in src/main/db/migrations/. The DB is created in Electron's userData directory as adiuva.db. On startup, initDb() runs non-destructive migrations (CREATE TABLE IF NOT EXISTS).
To add a new table or column: edit schema.ts, run drizzle-kit generate, then drizzle-kit push (dev) or commit the migration file.
Adding a New Feature (end-to-end pattern)
- Schema — Add table/columns to
src/main/db/schema.ts - Router — Add a tRPC sub-router in
src/main/router/index.ts, merge it intoappRouter - Types —
AppRouteris exported fromsrc/main/router/index.tsand imported insrc/renderer/lib/trpc.ts— types flow automatically - UI — Create components under
src/renderer/components/<feature>/, usetrpc.*.*useQuery()for data
AI Subsystem (src/main/ai/)
LangGraph-based agentic system with pluggable LLM providers (OpenAI, Anthropic, GitHub Copilot).
Orchestrator (orchestrator.ts): Classifies user intent → routes to one of three specialist agents:
- Project agent — project-scoped Q&A with tools:
read_project_notes,add_task,get_summary,suggest_checkpoints,suggest_tasks - Knowledge agent — cross-project semantic search via
vector_search_all - General agent — workspace-wide
add_task
Tool-calling strategy differs by provider: OpenAI/Anthropic use LangChain bindTools() + ToolMessage loop (max 5 iterations); Copilot uses SDK-native tools (loop handled internally).
Streaming: Orchestrator calls sendStreamChunk(sender, token, done) over IPC channel 'ai:stream'. Renderer subscribes via window.electronAI.onStreamChunk() in AIChatPanel.tsx. <tool_call> blocks are filtered before sending to renderer.
Provider factory (llm.ts): gpt-4o-mini (OpenAI), claude-sonnet-4-20250514 (Anthropic), or ChatCopilot wrapper — all with temperature: 0.3 and streaming enabled.
Token storage (token.ts) — three-tier fallback:
- keytar (OS keychain) — preferred, encrypted per-user
- electron-store +
safeStorage— encrypted at rest - Plain electron-store — WSL fallback
Keytar service name is 'adiuva'. Once keytar fails, keytarFailed flag skips it for the session.
AI approval pattern: Tasks and checkpoints have isAiSuggested (bool) and isApproved (bool) columns. AI-suggested items appear in the UI pending user approval before being treated as real records.
Vector Embeddings (src/main/db/vectordb.ts)
LanceDB stored in {userData}/vectors/. Table schema: { id, projectId, content, vector }. Vectors are 1536-dimensional (text-embedding-3-small). Embeddings use a priority chain: Copilot CLI token → OpenAI token.
- Note create/update fires
upsertNoteEmbedding()(fire-and-forget, errors swallowed) migrateNotesIfNeeded()backfills existing notes on first startupsearchNotes(query, limit=5)is called by the Knowledge agent tool
Key Config Notes
- Vite configs use
.mtsextension (not.ts) to avoid ESM/CJS conflicts with electron-forge's externalize-deps plugin @/*path alias resolves tosrc/renderer/*(TypeScript + Vite + shadcn/ui all share this alias)- shadcn/ui style: new-york, base color: neutral
- Icons: lucide-react throughout — do not introduce other icon libraries
- Tailwind 4 (not 3) — use CSS variable theming via
globals.css, nottailwind.config.js - Notes use Milkdown (
@milkdown/crepe) as the markdown editor (src/renderer/components/notes/MilkdownEditor.tsx) - Routes:
index,tasks,timeline,projects,notes.$noteId(note ID is a URL param)