From 0f3c63c4de16040c103cd2c677358407059f8e8a Mon Sep 17 00:00:00 2001 From: Roberto Musso Date: Sun, 1 Mar 2026 23:31:57 +0100 Subject: [PATCH] feat(settings): add permissions for git commands in settings.json --- .claude/CLAUDE.md | 209 +++++++++++++++++++++++------------------- .claude/settings.json | 8 ++ 2 files changed, 125 insertions(+), 92 deletions(-) create mode 100644 .claude/settings.json diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index aea9e9d..a884410 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -1,139 +1,164 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - ## Commands ```bash -# 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) +source ~/.nvm/nvm.sh && npm start # Dev with hot-reload +source ~/.nvm/nvm.sh && npm run make # Build distributable packages +source ~/.nvm/nvm.sh && npm run package # Package without installers +source ~/.nvm/nvm.sh && npm run lint # ESLint (.ts/.tsx) +source ~/.nvm/nvm.sh && npx drizzle-kit generate # Generate migration from schema +source ~/.nvm/nvm.sh && npx drizzle-kit push # Push schema directly (dev only) ``` -There is no test suite currently. +No test suite currently. -## Architecture Overview +## Architecture -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 +Adiuva is a local-first Electron desktop app. The three processes communicate via a custom tRPC v11 ↔ IPC bridge (the public `electron-trpc` package is incompatible with tRPC v11). ``` -Renderer (React) ──ipcLink──► Preload (contextBridge) ──IPC──► Main (tRPC router + SQLite) +Renderer (React 19) ──ipcLink──► Preload (contextBridge) ──IPC──► Main (tRPC router + SQLite) ``` -1. **Main process** (`src/main/`) — Node.js, owns the database and all business logic - - `index.ts` — Window creation, app lifecycle - - `ipc.ts` — Custom handler that bridges `ipcMain` to tRPC procedures - - `router/index.ts` — All tRPC routers (clients, projects, tasks, checkpoints, notes, settings, ai) - - `db/index.ts` — Drizzle + better-sqlite3, WAL mode, singleton `getDb()` - - `db/schema.ts` — All table definitions (clients, projects, tasks, checkpoints, notes) - - `store.ts` — electron-store for persistent UI settings (e.g., `sidebarCollapsed`) +### Main Process (`src/main/`) -2. **Preload** (`src/preload/trpc.ts`) — Exposes `window.electronTRPC` with `sendMessage()` / `onMessage()` +Owns the database and all business logic. -3. **Renderer** (`src/renderer/`) — React 19, never accesses Node APIs directly - - `lib/ipcLink.ts` — Custom TRPCLink that routes calls through `window.electronTRPC` - - `lib/trpc.ts` — `createTRPCReact()` typed client - - `index.tsx` — QueryClient + tRPC + Router providers - - All data access is through `trpc.*.*useQuery()` / `trpc.*.*.useMutation()` +| File | Purpose | +|---|---| +| `index.ts` | Window creation, app lifecycle | +| `ipc.ts` | Bridges `ipcMain` to tRPC procedures | +| `router/index.ts` | All tRPC sub-routers merged into `appRouter` | +| `db/index.ts` | Drizzle + better-sqlite3, WAL mode, singleton `getDb()` | +| `db/schema.ts` | Table definitions: clients, projects, tasks, checkpoints, notes, taskComments | +| `db/vectordb.ts` | LanceDB vector store for note embeddings | +| `store.ts` | electron-store for persistent UI settings | + +### Preload (`src/preload/trpc.ts`) + +Exposes `window.electronTRPC` with `sendMessage()` / `onMessage()`. + +### Renderer (`src/renderer/`) + +React 19 — never accesses Node APIs directly. All data through `trpc.*.useQuery()` / `trpc.*.useMutation()`. + +| File | Purpose | +|---|---| +| `lib/ipcLink.ts` | Custom TRPCLink routing through `window.electronTRPC` | +| `lib/trpc.ts` | `createTRPCReact()` typed client | +| `index.tsx` | QueryClient + tRPC + Router providers | ### 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 in `AppShell` -- `index.tsx`, `tasks.tsx`, `timeline.tsx`, `projects.tsx` +File-based via TanStack Router (`tsr.config.json` at root). Route tree auto-generated at `routeTree.gen.ts`. + +Routes: `__root.tsx` (AppShell layout), `index`, `tasks`, `timeline`, `projects`, `notes.$noteId` + +### tRPC Routers + +`health`, `settings`, `clients`, `projects`, `tasks`, `checkpoints`, `notes`, `taskComments`, `ai` ### 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). +Schema in `src/main/db/schema.ts`, migrations in `src/main/db/migrations/`. DB created in Electron's `userData` as `adiuva.db`. On startup, `initDb()` runs non-destructive migrations. -To add a new table or column: edit `schema.ts`, run `drizzle-kit generate`, then `drizzle-kit push` (dev) or commit the migration file. +To add a table/column: edit `schema.ts` → `drizzle-kit generate` → `drizzle-kit push` (dev) or commit the migration. -### Adding a New Feature (end-to-end pattern) +### Adding a Feature (end-to-end) -1. **Schema** — Add table/columns to `src/main/db/schema.ts` -2. **Router** — Add a tRPC sub-router in `src/main/router/index.ts`, merge it into `appRouter` -3. **Types** — `AppRouter` is exported from `src/main/router/index.ts` and imported in `src/renderer/lib/trpc.ts` — types flow automatically -4. **UI** — Create components under `src/renderer/components//`, use `trpc.*.*useQuery()` for data +1. **Schema** — `src/main/db/schema.ts` +2. **Router** — Add sub-router in `src/main/router/index.ts`, merge into `appRouter` +3. **Types** — Flow automatically via `AppRouter` export +4. **UI** — Components in `src/renderer/components//`, data via `trpc.*.useQuery()` -### AI Subsystem (`src/main/ai/`) +## AI Subsystem (`src/main/ai/`) -LangGraph-based agentic system with pluggable LLM providers (OpenAI, Anthropic, GitHub Copilot). +LangGraph-based agentic system with pluggable LLM providers. -**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` +### Orchestrator (`orchestrator.ts`) -Tool-calling strategy differs by provider: OpenAI/Anthropic use LangChain `bindTools()` + ToolMessage loop (max 5 iterations); Copilot uses SDK-native tools (loop handled internally). +Classifies user intent → routes to a specialist agent: -**Streaming**: Orchestrator calls `sendStreamChunk(sender, token, done)` over IPC channel `'ai:stream'`. Renderer subscribes via `window.electronAI.onStreamChunk()` in `AIChatPanel.tsx`. `` blocks are filtered before sending to renderer. +| Agent | Scope | Tools | +|---|---|---| +| Project | Project-scoped Q&A | `read_project_notes`, `add_task`, `get_summary`, `suggest_checkpoints`, `suggest_tasks` | +| Knowledge | Cross-project search | `vector_search_all` | +| General | Workspace-wide | `add_task` | -**Provider factory** (`llm.ts`): `gpt-4o-mini` (OpenAI), `claude-sonnet-4-20250514` (Anthropic), or ChatCopilot wrapper — all with `temperature: 0.3` and streaming enabled. +All providers use LangChain `bindTools()` + ToolMessage loop (max 5 iterations). -**Token storage** (`token.ts`) — three-tier fallback: -1. keytar (OS keychain) — preferred, encrypted per-user +Also exports `dailyBrief()` for AI-generated daily summaries (`ai.dailyBrief` tRPC mutation). + +### Streaming + +`sendStreamChunk(sender, token, done)` over IPC `'ai:stream'`. Renderer subscribes via `window.electronAI.onStreamChunk()` in `AIChatPanel.tsx`. `` blocks are filtered before display. + +### Providers (`llm.ts`) + +| Provider | Model | Notes | +|---|---|---| +| OpenAI | `gpt-4o-mini` | Via LangChain | +| Anthropic | `claude-sonnet-4-20250514` | Via LangChain | +| Copilot | `ChatCopilot` wrapper | `copilot.ts` / `chat-copilot.ts` | + +All use `temperature: 0.3`, streaming enabled. Provider management in `provider.ts`. + +### Token Storage (`token.ts`) + +Three-tier fallback (keytar service name: `'adiuva'`): +1. keytar (OS keychain) — preferred; `keytarFailed` flag skips after first failure 2. electron-store + `safeStorage` — encrypted at rest 3. Plain electron-store — WSL fallback -Keytar service name is `'adiuva'`. Once keytar fails, `keytarFailed` flag skips it for the session. +### Vector Embeddings (`db/vectordb.ts`) -**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. +LanceDB in `{userData}/vectors/`. Schema: `{ id, projectId, content, vector }` (1536-dim, `text-embedding-3-small` via `embeddings.ts`). Embedding priority: Copilot CLI token → OpenAI token. -### Vector Embeddings (`src/main/db/vectordb.ts`) +- `upsertNoteEmbedding()` on note create/update (fire-and-forget) +- `migrateNotesIfNeeded()` backfills on first startup +- `searchNotes(query, limit=5)` used by Knowledge agent -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. +### AI Approval Pattern -- Note create/update fires `upsertNoteEmbedding()` (fire-and-forget, errors swallowed) -- `migrateNotesIfNeeded()` backfills existing notes on first startup -- `searchNotes(query, limit=5)` is called by the Knowledge agent tool +Tasks and checkpoints have `isAiSuggested` + `isApproved` columns. AI suggestions appear pending user approval (dashed borders in UI). -### Key Config Notes +## Config Notes -- Vite configs use `.mts` extension (not `.ts`) to avoid ESM/CJS conflicts with electron-forge's externalize-deps plugin -- `@/*` path alias resolves to `src/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`, not `tailwind.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) +- Vite configs use `.mts` (not `.ts`) — avoids ESM/CJS conflicts with electron-forge +- `@/*` path alias → `src/renderer/*` (TypeScript + Vite + shadcn/ui) +- **shadcn/ui**: new-york style, neutral base color +- **Icons**: lucide-react only — do not introduce other icon libraries +- **Tailwind 4** — CSS variable theming in `globals.css`, no `tailwind.config.js` +- **Notes editor**: Milkdown (`@milkdown/crepe`) at `src/renderer/components/notes/MilkdownEditor.tsx` ## Design Context -### Users -Freelancers and solo professionals managing their own client work — projects, tasks, notes, and timelines. They work alone and need a single workspace that keeps everything organized without the overhead of enterprise tools. The AI assistant is a force multiplier, helping them stay on top of their workload. +### Target User +Freelancers and solo professionals managing client work (projects, tasks, notes, timelines). Single workspace, no enterprise overhead. AI as force multiplier. -### Brand Personality -**Calm, intelligent, warm.** Adiuva is a thoughtful companion, not a flashy tool. It should feel like a well-organized desk — everything in its place, nothing competing for attention. The tone is confident and understated, never loud or gamified. +### Brand +**Calm, intelligent, warm.** Thoughtful companion, not flashy tool. Confident and understated, never loud or gamified. -### Aesthetic Direction -- **Visual tone**: Editorial, premium, content-first. Inspired by Notion's clean typography and warm neutrals, but with a distinct identity through the warm pinkish-white canvas and golden yellow accent -- **Light mode**: Soft and warm — pinkish-white (`#f4edf3`) canvas, golden yellow (`#fbc881`) primary, slate blue-gray (`#8a8ea9`) secondary, dusty lavender borders (`#c8c3cd`) -- **Dark mode**: Stark monochrome — near-black canvas (`#0c0c0c`), crisp white text, dark gray surfaces (`#323232`). No color accent; primary is pure white -- **Typography**: Geist (geometric sans-serif) at 400/500/600. Tight tracking on large headings (`-1px`). Body at `text-sm`, metadata at `text-xs` -- **Corners**: 10px base radius, consistently rounded. Chat elements use `rounded-2xl` -- **Signature effects**: Glassmorphism on AI inputs/floating chat (`backdrop-blur-xl`, transparency). Spring physics animations (stiffness 400, damping 30). Subtle scale-and-fade transitions -- **Anti-references**: No gamification (badges, streaks, confetti). No corporate/enterprise density. Keep it mature and professional +### Palette + +| | Canvas | Primary | Secondary | Borders | +|---|---|---|---|---| +| **Light** | Pinkish-white `#f4edf3` | Golden yellow `#fbc881` | Slate blue-gray `#8a8ea9` | Dusty lavender `#c8c3cd` | +| **Dark** | Near-black `#0c0c0c` | Pure white | — | Dark gray `#323232` | + +### Typography +Geist sans-serif, weights 400/500/600. Tight tracking (`-1px`) on headings. Body `text-sm`, metadata `text-xs`. + +### Visual Language +- 10px border-radius, `rounded-2xl` for chat elements +- Glassmorphism on AI inputs (`backdrop-blur-xl`, transparency) +- Spring animations (stiffness 400, damping 30), scale-and-fade transitions +- No gamification (badges, streaks, confetti). Mature and professional ### Design Principles - -1. **Clarity over cleverness** — Every element should communicate its purpose instantly. Prefer clear hierarchy and whitespace over decorative flourish. Information density should feel comfortable, not cramped. - -2. **AI as quiet partner** — The AI is deeply integrated (floating chat, suggestions) but never intrusive. AI-suggested items use dashed borders to signal "pending." The Sparkles icon is the consistent AI identity marker. - -3. **Warmth in restraint** — The palette is deliberately warm (pinkish whites, golden yellows) to feel approachable without being playful. Dark mode trades warmth for focus. Let the content breathe. - -4. **Motion with purpose** — Spring physics and glassmorphism create a sense of physicality and depth. Animations should feel natural and responsive, never decorative or slow. Every transition should reinforce spatial relationships. - -5. **Confidence through consistency** — Use the established token system (CSS variables, shadcn/ui primitives, Geist font). The user should feel in control — predictable patterns, keyboard-first interactions, no surprises. \ No newline at end of file +1. **Clarity over cleverness** — Clear hierarchy, generous whitespace, comfortable density +2. **AI as quiet partner** — Deeply integrated but never intrusive. Dashed borders for pending AI items, Sparkles icon as AI marker +3. **Warmth in restraint** — Warm palette feels approachable without being playful. Dark mode trades warmth for focus +4. **Motion with purpose** — Animations reinforce spatial relationships, never decorative +5. **Confidence through consistency** — CSS variable tokens, shadcn/ui primitives, Geist font. Predictable, keyboard-first diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..86bac4d --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(git add AI_REFACTOR_PLAN.md)", + "Bash(git commit:*)" + ] + } +}