--- project_name: 'Keep (Memento Phase 1 MVP AI)' user_name: 'Ramez' date: '2026-01-10' sections_completed: ['technology_stack', 'language_rules', 'framework_rules', 'testing_rules', 'quality_rules', 'workflow_rules', 'anti_patterns'] status: 'complete' rule_count: 50 optimized_for_llm: true workflow_type: 'generate-project-context' --- # Project Context for AI Agents _This file contains critical rules and patterns that AI agents must follow when implementing code in this project. Focus on unobvious details that agents might otherwise miss._ --- ## Technology Stack & Versions ### Core Framework **Frontend:** - **Next.js:** 16.1.1 (App Router) - **React:** 19.2.3 - **TypeScript:** 5.x (strict mode enabled) **Backend:** - **Next.js API Routes** (REST) - **Server Actions** ('use server' directive) - **Prisma:** 5.22.0 (ORM) - **Database:** SQLite (better-sqlite3) **Authentication:** - **NextAuth:** 5.0.0-beta.30 - **Adapter:** @auth/prisma-adapter **AI/ML:** - **Vercel AI SDK:** 6.0.23 - **OpenAI Provider:** @ai-sdk/openai ^3.0.7 - **Ollama Provider:** ollama-ai-provider ^1.2.0 - **Language Detection:** tinyld (to be installed for Phase 1) **UI Components:** - **Radix UI:** Multiple primitives (@radix-ui/react-*) - **Tailwind CSS:** 4.x - **Lucide Icons:** ^0.562.0 - **Sonner:** ^2.0.7 (toast notifications) **Utilities:** - **Zod:** ^4.3.5 (schema validation) - **date-fns:** ^4.1.0 (date formatting) - **clsx:** ^2.1.1, **tailwind-merge:** ^3.4.0 (CSS utilities) - **katex:** ^0.16.27 (LaTeX rendering) - **react-markdown:** ^10.1.0 (markdown rendering) **Drag & Drop:** - **@dnd-kit:** ^6.3.1 (modern DnD library) - **muuri:** ^0.9.5 (masonry grid layout) **Testing:** - **Playwright:** ^1.57.0 (E2E tests) --- ## Critical Implementation Rules ### TypeScript Configuration **STRICT MODE ENABLED:** ```json { "strict": true, "target": "ES2017", "moduleResolution": "bundler", "jsx": "react-jsx", "paths": { "@/*": ["./*"] } } ``` **CRITICAL RULES:** - ✅ All files MUST be typed (no `any` without explicit reason) - ✅ Use `interface` for object shapes, `type` for unions/primitives - ✅ Import from `@/` alias (not relative paths like `../`) - ✅ Props MUST be typed with interfaces (PascalCase names) **Example:** ```typescript // ✅ GOOD interface NoteCardProps { note: Note onEdit?: (note: Note) => void } export function NoteCard({ note, onEdit }: NoteCardProps) { // ... } // ❌ BAD - any without reason export function NoteCard({ note, onEdit }: any) { // ... } ``` --- ### Component Patterns **Directives Required:** - ✅ Server Components: No directive (default in Next.js 16 App Router) - ✅ Client Components: `'use client'` at TOP of file (line 1) - ✅ Server Actions: `'use server'` at TOP of file (line 1) **Example:** ```typescript // keep-notes/components/ai/ai-suggestion.tsx 'use client' import { useState } from 'react' import { Button } from '@/components/ui/button' export function AiSuggestion() { // Interactive component logic } ``` **Component Naming:** - ✅ **PascalCase** for component names: `NoteCard`, `LabelBadge`, `AiSuggestion` - ✅ **kebab-case** for file names: `note-card.tsx`, `label-badge.tsx`, `ai-suggestion.tsx` - ✅ **UI components** in `components/ui/` subdirectory: `button.tsx`, `dialog.tsx` **Props Pattern:** ```typescript // ✅ GOOD - Interface export export interface NoteCardProps { note: Note onEdit?: (note: Note, readOnly?: boolean) => void isDragging?: boolean } export function NoteCard({ note, onEdit, isDragging }: NoteCardProps) { // ... } ``` **Imports Order:** ```typescript // 1. React imports import { useState, useEffect } from 'react' // 2. Third-party libraries import { formatDistanceToNow } from 'date-fns' import { Bell } from 'lucide-react' // 3. Local imports (use @/ alias) import { Card } from '@/components/ui/card' import { Note } from '@/lib/types' import { deleteNote } from '@/app/actions/notes' ``` --- ### Server Actions Pattern **CRITICAL: All server actions MUST follow this pattern:** ```typescript // keep-notes/app/actions/ai-suggestions.ts 'use server' import { auth } from '@/auth' import { revalidatePath } from 'next/cache' import { prisma } from '@/lib/prisma' export async function generateTitleSuggestions(noteId: string) { // 1. Authentication check const session = await auth() if (!session?.user?.id) { throw new Error('Unauthorized') } try { // 2. Business logic const note = await prisma.note.findUnique({ where: { id: noteId } }) if (!note) { throw new Error('Note not found') } // 3. AI processing const titles = await generateTitles(note.content) // 4. Revalidate cache revalidatePath('/') return { success: true, titles } } catch (error) { console.error('Error generating titles:', error) throw new Error('Failed to generate title suggestions') } } ``` **CRITICAL RULES:** - ✅ `'use server'` at line 1 (before imports) - ✅ **ALWAYS** check `auth()` session first - ✅ **ALWAYS** `revalidatePath('/')` after mutations - ✅ Use `try/catch` with `console.error()` logging - ✅ Throw `Error` objects (not strings) - ✅ Return `{ success: true, data }` or throw error --- ### API Routes Pattern **CRITICAL: All API routes MUST follow this pattern:** ```typescript // keep-notes/app/api/ai/titles/route.ts import { NextRequest, NextResponse } from 'next/server' import { z } from 'zod' const requestSchema = z.object({ content: z.string().min(1, "Content required"), noteId: z.string().optional() }) export async function POST(req: NextRequest) { try { // 1. Parse and validate request const body = await req.json() const { content, noteId } = requestSchema.parse(body) // 2. Business logic const titles = await generateTitles(content) // 3. Return success response return NextResponse.json({ success: true, data: { titles } }) } catch (error) { // 4. Error handling if (error instanceof z.ZodError) { return NextResponse.json( { success: false, error: error.issues }, { status: 400 } ) } console.error('Error generating titles:', error) return NextResponse.json( { success: false, error: 'Failed to generate titles' }, { status: 500 } ) } } ``` **CRITICAL RULES:** - ✅ Use **Zod schemas** for request validation - ✅ Return `{ success: true, data: any }` for success - ✅ Return `{ success: false, error: string }` for errors - ✅ Handle `ZodError` separately (400 status) - ✅ Log errors with `console.error()` - ✅ **NEVER** expose stack traces to clients **Response Format:** ```typescript // Success { success: true, data: { ... } } // Error { success: false, error: "Human-readable error message" } ``` --- ### Database Access Pattern **SINGLE DATA ACCESS LAYER:** - ✅ **ONLY** use Prisma ORM (no raw SQL, no direct database access) - ✅ Import from `@/lib/prisma` (singleton instance) - ✅ Use `findMany`, `findUnique`, `create`, `update`, `delete` ```typescript // ✅ GOOD import { prisma } from '@/lib/prisma' const notes = await prisma.note.findMany({ where: { userId: session.user.id }, orderBy: { createdAt: 'desc' } }) ``` **Prisma Schema Conventions:** - ✅ **PascalCase** for model names: `User`, `Note`, `Label`, `AiFeedback` - ✅ **camelCase** for fields: `userId`, `isPinned`, `createdAt` - ✅ Foreign keys: `{model}Id` format: `userId`, `noteId` - ✅ Booleans: prefix `is` for flags: `isPinned`, `isArchived` - ✅ Timestamps: suffix `At` for dates: `createdAt`, `updatedAt` - ✅ All new fields optional (nullable) for backward compatibility --- ### Naming Conventions **Database:** - Tables: **PascalCase** (`AiFeedback`, `MemoryEchoInsight`) - Columns: **camelCase** (`noteId`, `similarityScore`) - Indexes: Prisma `@@index([...])` annotations **API Routes:** - Collections: **plural** (`/api/notes`, `/api/labels`) - Items: **singular** (`/api/notes/[id]`) - Namespace: `/api/ai/*` for AI features **Components:** - Component names: **PascalCase** (`NoteCard`, `AiSuggestion`) - File names: **kebab-case** (`note-card.tsx`, `ai-suggestion.tsx`) **Functions:** - Functions: **camelCase** (`getNotes`, `createNote`, `togglePin`) - Verbs first: `get`, `create`, `update`, `delete`, `toggle` - Handlers: prefix `handle` (`handleDelete`, `handleTogglePin`) **Variables:** - Variables: **camelCase** (`userId`, `isPending`, `noteId`) - Types/interfaces: **PascalCase** (`Note`, `NoteCardProps`) --- ### State Management **NO GLOBAL STATE LIBRARIES:** - ❌ No Redux, Zustand, or similar - ✅ **React useState** for local component state - ✅ **React Context** for shared state (User session, Theme, Labels) - ✅ **React Cache** for server-side caching - ✅ **useOptimistic** for immediate UI feedback - ✅ **useTransition** for non-blocking updates **Example:** ```typescript 'use client' import { useState, useTransition, useOptimistic } from 'react' export function NoteCard({ note }: NoteCardProps) { const [isPending, startTransition] = useTransition() const [optimisticNote, addOptimisticNote] = useOptimistic( note, (state, newProps: Partial) => ({ ...state, ...newProps }) ) const handleTogglePin = async () => { startTransition(async () => { addOptimisticNote({ isPinned: !note.isPinned }) await togglePin(note.id, !note.isPinned) router.refresh() }) } } ``` --- ### Error Handling **Global Pattern:** ```typescript // API Routes try { // ... code } catch (error) { console.error('Feature name error:', error) return NextResponse.json( { success: false, error: 'Human-readable message' }, { status: 500 } ) } // Server Actions try { // ... code } catch (error) { console.error('Feature name error:', error) throw new Error('Failed to action') } ``` **CRITICAL RULES:** - ✅ Use `console.error()` for logging (not `console.log`) - ✅ Human-readable error messages (no technical jargon) - ✅ **NEVER** expose stack traces to users - ✅ **NEVER** expose internal error details --- ### Import Rules **ALWAYS use @/ alias:** ```typescript // ✅ GOOD import { Button } from '@/components/ui/button' import { Note } from '@/lib/types' import { deleteNote } from '@/app/actions/notes' // ❌ BAD - relative paths import { Button } from '../../../components/ui/button' import { Note } from '../lib/types' ``` **Import from Radix UI:** ```typescript // ✅ GOOD - use @/components/ui/* wrapper import { Dialog } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' // ❌ BAD - direct Radix imports import { Dialog } from '@radix-ui/react-dialog' import { Button } from '@radix-ui/react-slot' ``` --- ### AI Service Pattern **All AI services follow this structure:** ```typescript // keep-notes/lib/ai/services/title-suggestion.service.ts import { getAIProvider } from '@/lib/ai/factory' export class TitleSuggestionService { private provider = getAIProvider() async generateSuggestions(content: string): Promise { try { const response = await this.provider.generateText({ prompt: `Generate 3 titles for: ${content}`, maxTokens: 100 }) return response.titles } catch (error) { console.error('TitleSuggestionService error:', error) throw new Error('Failed to generate suggestions') } } } ``` **CRITICAL RULES:** - ✅ Use `getAIProvider()` factory (not direct OpenAI/Ollama imports) - ✅ Services are **stateless classes** - ✅ Constructor injection of dependencies - ✅ Methods return `Promise` with error handling - ✅ No direct database access (via Prisma) --- ### Testing Rules **Playwright E2E Tests:** ```typescript // tests/e2e/ai-features.spec.ts import { test, expect } from '@playwright/test' test('AI title suggestions appear', async ({ page }) => { await page.goto('/') await page.fill('[data-testid="note-content"]', 'Test content') // Wait for AI suggestions await expect(page.locator('[data-testid="ai-suggestions"]')).toBeVisible() }) ``` **CRITICAL RULES:** - ✅ Use `data-testid` attributes for test selectors - ✅ Test critical user flows (not edge cases) - ✅ Use `await expect(...).toBeVisible()` for assertions - ✅ Tests in `tests/e2e/` directory --- ### Brownfield Integration Rules **ZERO BREAKING CHANGES:** - ✅ **ALL new features must extend, not replace existing functionality** - ✅ Existing components, API routes, and database tables MUST continue working - ✅ New database fields: **optional** (nullable) for backward compatibility - ✅ New features: **additive** only (don't remove existing features) **Example:** ```prisma // ✅ GOOD - optional new field model Note { // ... existing fields language String? // NEW: optional aiConfidence Int? // NEW: optional } // ❌ BAD - breaking change model Note { language String @default("en") // BREAKS: non-optional default } ``` --- ### Phase 1 Specific Rules **AI Features to Implement:** 1. **Title Suggestions** - 3 suggestions after 50+ words 2. **Semantic Search** - Hybrid keyword + vector search with RRF 3. **Paragraph Reformulation** - Clarify, Shorten, Improve Style options 4. **Memory Echo** - Daily proactive note connections (background job) 5. **AI Settings** - Granular ON/OFF controls per feature 6. **Language Detection** - TinyLD hybrid (< 50 words: library, ≥ 50 words: AI) **Performance Targets:** - ✅ Title suggestions: < 2s after detection - ✅ Semantic search: < 300ms for 1000 notes - ✅ Memory Echo: < 100ms UI freeze (background processing) - ✅ Language detection: ~8ms (TinyLD) or ~200-500ms (AI) **Language Support:** - ✅ System prompts: **English** (stability) - ✅ User data: **Local language** (FR, EN, ES, DE, FA/Persian + 57 others) - ✅ TinyLD supports 62 languages including Persian (verified) --- ### Security Rules **API Keys:** - ✅ **NEVER** expose API keys to client (server-side only) - ✅ Store in environment variables (`OPENAI_API_KEY`, `OLLAMA_ENDPOINT`) - ✅ Use SystemConfig table for provider selection **Authentication:** - ✅ **ALL** server actions check `auth()` session first - ✅ **ALL** API routes require valid NextAuth session - ✅ Public routes: `/api/auth/*`, login/register pages only **Privacy:** - ✅ Ollama path = 100% local (no external API calls) - ✅ OpenAI path = cloud (verify in DevTools Network tab) - ✅ User data never logged or exposed --- ### File Organization **AI Services:** ``` lib/ai/services/ ├── title-suggestion.service.ts ├── semantic-search.service.ts ├── paragraph-refactor.service.ts ├── memory-echo.service.ts ├── language-detection.service.ts └── embedding.service.ts ``` **AI Components:** ``` components/ai/ ├── ai-suggestion.tsx ├── ai-settings-panel.tsx ├── memory-echo-notification.tsx ├── confidence-badge.tsx ├── feedback-buttons.tsx └── paragraph-refactor.tsx ``` **API Routes:** ``` app/api/ai/ ├── titles/route.ts ├── search/route.ts ├── refactor/route.ts ├── echo/route.ts ├── feedback/route.ts └── language/route.ts ``` --- ### Development Workflow **Before implementing ANY feature:** 1. ✅ Read `_bmad-output/planning-artifacts/architecture.md` 2. ✅ Check `project-context.md` for specific rules 3. ✅ Follow naming patterns (camelCase, PascalCase, kebab-case) 4. ✅ Use response format `{success, data, error}` 5. ✅ Add `'use server'` or `'use client'` directives 6. ✅ Import from `@/` alias only **Quality Checklist:** - [ ] TypeScript strict mode compliance - [ ] Zod validation for API routes - [ ] auth() check in server actions - [ ] revalidatePath('/') after mutations - [ ] Error handling with console.error() - [ ] Response format {success, data/error} - [ ] Import from @/ alias - [ ] Component directives ('use client'/'use server') - [ ] Zero breaking changes - [ ] Performance targets met --- ## Quick Reference Card **For AI Agents implementing features:** | Category | Rule | Example | |----------|------|---------| | TypeScript | Strict mode, no `any` | `interface Props { note: Note }` | | Components | 'use client' at top | `export function Comp() { ... }` | | Server Actions | 'use server' + auth() + revalidate | `const session = await auth()` | | API Routes | Zod + {success, data/error} | `return NextResponse.json({ success: true, data })` | | Database | Prisma only, no raw SQL | `await prisma.note.findMany()` | | Naming | camelCase vars, PascalCase types | `const userId: string` | | Imports | @/ alias only | `import { Note } from '@/lib/types'` | | Error Handling | console.error + human message | `throw new Error('Failed to...')` | | AI Services | getAIProvider() factory | `const provider = getAIProvider()` | | Performance | Target < 2s for AI features | `await withTimeout(promise, 2000)` | --- *Generated: 2026-01-10* *Project: Keep (Memento Phase 1 MVP AI)* *Architecture: Based on architecture.md (2784 lines)* *Status: Ready for AI Agent Implementation* --- ## Usage Guidelines **For AI Agents:** - ✅ Read this file **before** implementing any code - ✅ Follow **ALL** rules exactly as documented - ✅ When in doubt, prefer the more restrictive option - ✅ Check `_bmad-output/planning-artifacts/architecture.md` for full context - ✅ Use response format `{success, data, error}` for API routes - ✅ Add `'use server'` or `'use client'` directives at top of files - ✅ Import from `@/` alias only (not relative paths) - ✅ Validate requests with Zod schemas - ✅ Check `auth()` session in server actions - ✅ Call `revalidatePath('/')` after mutations - ✅ Log errors with `console.error()` **For Humans:** - Keep this file **lean and focused** on agent needs - Update when **technology stack changes** - Review **quarterly** for outdated rules - Remove rules that become **obvious over time** - Add new patterns when they emerge in development **Maintenance:** 1. **Technology Changes:** Update when adding/removing dependencies 2. **Pattern Evolution:** Add new patterns as they emerge 3. **Bug Prevention:** Add rules when agents make common mistakes 4. **Optimization:** Remove redundant or obvious rules periodically 5. **Review Cycle:** Check quarterly for outdated information --- **Last Updated:** 2026-01-10 **Optimization Status:** ✅ Optimized for LLM context (50 critical rules, 490 lines) **Readiness:** ✅ Ready for AI Agent Implementation --- *Workflow completed: 2026-01-10* *Generator: Winston (Architect Agent) with Generate Project Context workflow* *Based on: architecture.md (2784 lines) + existing codebase analysis*