feat(settings): add permissions for git commands in settings.json
This commit is contained in:
@@ -1,139 +1,164 @@
|
|||||||
# CLAUDE.md
|
# CLAUDE.md
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Development
|
source ~/.nvm/nvm.sh && npm start # Dev with hot-reload
|
||||||
source ~/.nvm/nvm.sh && npm start # Start Electron app with hot-reload
|
source ~/.nvm/nvm.sh && npm run make # Build distributable packages
|
||||||
|
source ~/.nvm/nvm.sh && npm run package # Package without installers
|
||||||
# Build & Package
|
source ~/.nvm/nvm.sh && npm run lint # ESLint (.ts/.tsx)
|
||||||
source ~/.nvm/nvm.sh && npm run make # Build distributable packages
|
source ~/.nvm/nvm.sh && npx drizzle-kit generate # Generate migration from schema
|
||||||
source ~/.nvm/nvm.sh && npm run package # Package without making installers
|
source ~/.nvm/nvm.sh && npx drizzle-kit push # Push schema directly (dev only)
|
||||||
|
|
||||||
# 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.
|
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).
|
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).
|
||||||
|
|
||||||
### Process Boundaries
|
|
||||||
|
|
||||||
```
|
```
|
||||||
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
|
### Main Process (`src/main/`)
|
||||||
- `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`)
|
|
||||||
|
|
||||||
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
|
| File | Purpose |
|
||||||
- `lib/ipcLink.ts` — Custom TRPCLink that routes calls through `window.electronTRPC`
|
|---|---|
|
||||||
- `lib/trpc.ts` — `createTRPCReact<AppRouter>()` typed client
|
| `index.ts` | Window creation, app lifecycle |
|
||||||
- `index.tsx` — QueryClient + tRPC + Router providers
|
| `ipc.ts` | Bridges `ipcMain` to tRPC procedures |
|
||||||
- All data access is through `trpc.*.*useQuery()` / `trpc.*.*.useMutation()`
|
| `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<AppRouter>()` typed client |
|
||||||
|
| `index.tsx` | QueryClient + tRPC + Router providers |
|
||||||
|
|
||||||
### Routing
|
### 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:
|
File-based via TanStack Router (`tsr.config.json` at root). Route tree auto-generated at `routeTree.gen.ts`.
|
||||||
- `__root.tsx` — Root layout wrapping everything in `AppShell`
|
|
||||||
- `index.tsx`, `tasks.tsx`, `timeline.tsx`, `projects.tsx`
|
Routes: `__root.tsx` (AppShell layout), `index`, `tasks`, `timeline`, `projects`, `notes.$noteId`
|
||||||
|
|
||||||
|
### tRPC Routers
|
||||||
|
|
||||||
|
`health`, `settings`, `clients`, `projects`, `tasks`, `checkpoints`, `notes`, `taskComments`, `ai`
|
||||||
|
|
||||||
### Database
|
### 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`
|
1. **Schema** — `src/main/db/schema.ts`
|
||||||
2. **Router** — Add a tRPC sub-router in `src/main/router/index.ts`, merge it into `appRouter`
|
2. **Router** — Add sub-router in `src/main/router/index.ts`, merge into `appRouter`
|
||||||
3. **Types** — `AppRouter` is exported from `src/main/router/index.ts` and imported in `src/renderer/lib/trpc.ts` — types flow automatically
|
3. **Types** — Flow automatically via `AppRouter` export
|
||||||
4. **UI** — Create components under `src/renderer/components/<feature>/`, use `trpc.*.*useQuery()` for data
|
4. **UI** — Components in `src/renderer/components/<feature>/`, 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:
|
### Orchestrator (`orchestrator.ts`)
|
||||||
- **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).
|
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`. `<tool_call>` 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:
|
Also exports `dailyBrief()` for AI-generated daily summaries (`ai.dailyBrief` tRPC mutation).
|
||||||
1. keytar (OS keychain) — preferred, encrypted per-user
|
|
||||||
|
### Streaming
|
||||||
|
|
||||||
|
`sendStreamChunk(sender, token, done)` over IPC `'ai:stream'`. Renderer subscribes via `window.electronAI.onStreamChunk()` in `AIChatPanel.tsx`. `<tool_call>` 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
|
2. electron-store + `safeStorage` — encrypted at rest
|
||||||
3. Plain electron-store — WSL fallback
|
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)
|
Tasks and checkpoints have `isAiSuggested` + `isApproved` columns. AI suggestions appear pending user approval (dashed borders in UI).
|
||||||
- `migrateNotesIfNeeded()` backfills existing notes on first startup
|
|
||||||
- `searchNotes(query, limit=5)` is called by the Knowledge agent tool
|
|
||||||
|
|
||||||
### Key Config Notes
|
## Config Notes
|
||||||
|
|
||||||
- Vite configs use `.mts` extension (not `.ts`) to avoid ESM/CJS conflicts with electron-forge's externalize-deps plugin
|
- Vite configs use `.mts` (not `.ts`) — avoids ESM/CJS conflicts with electron-forge
|
||||||
- `@/*` path alias resolves to `src/renderer/*` (TypeScript + Vite + shadcn/ui all share this alias)
|
- `@/*` path alias → `src/renderer/*` (TypeScript + Vite + shadcn/ui)
|
||||||
- shadcn/ui style: **new-york**, base color: **neutral**
|
- **shadcn/ui**: new-york style, neutral base color
|
||||||
- Icons: **lucide-react** throughout — do not introduce other icon libraries
|
- **Icons**: lucide-react only — do not introduce other icon libraries
|
||||||
- Tailwind 4 (not 3) — use CSS variable theming via `globals.css`, not `tailwind.config.js`
|
- **Tailwind 4** — CSS variable theming in `globals.css`, no `tailwind.config.js`
|
||||||
- Notes use Milkdown (`@milkdown/crepe`) as the markdown editor (`src/renderer/components/notes/MilkdownEditor.tsx`)
|
- **Notes editor**: Milkdown (`@milkdown/crepe`) at `src/renderer/components/notes/MilkdownEditor.tsx`
|
||||||
- Routes: `index`, `tasks`, `timeline`, `projects`, `notes.$noteId` (note ID is a URL param)
|
|
||||||
|
|
||||||
## Design Context
|
## Design Context
|
||||||
|
|
||||||
### Users
|
### Target User
|
||||||
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.
|
Freelancers and solo professionals managing client work (projects, tasks, notes, timelines). Single workspace, no enterprise overhead. AI as force multiplier.
|
||||||
|
|
||||||
### Brand Personality
|
### Brand
|
||||||
**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.
|
**Calm, intelligent, warm.** Thoughtful companion, not flashy tool. Confident and understated, never loud or gamified.
|
||||||
|
|
||||||
### Aesthetic Direction
|
### Palette
|
||||||
- **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`)
|
| | Canvas | Primary | Secondary | Borders |
|
||||||
- **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`
|
| **Light** | Pinkish-white `#f4edf3` | Golden yellow `#fbc881` | Slate blue-gray `#8a8ea9` | Dusty lavender `#c8c3cd` |
|
||||||
- **Corners**: 10px base radius, consistently rounded. Chat elements use `rounded-2xl`
|
| **Dark** | Near-black `#0c0c0c` | Pure white | — | Dark gray `#323232` |
|
||||||
- **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
|
### 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
|
### Design Principles
|
||||||
|
1. **Clarity over cleverness** — Clear hierarchy, generous whitespace, comfortable density
|
||||||
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** — 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
|
||||||
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.
|
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
|
||||||
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.
|
|
||||||
|
|||||||
8
.claude/settings.json
Normal file
8
.claude/settings.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(git add AI_REFACTOR_PLAN.md)",
|
||||||
|
"Bash(git commit:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user