CodeAI.md Logo

CodeAI.md - Context as Infrastructure

Give Your AI Coding Assistant Deep Codebase Understanding

AI coding assistants generate generic code when they lack context about your specific codebase. CodeAI.md gives you structured templates to document your architecture, naming conventions, and integration patterns in markdown that AI tools consume automatically.

Capture your file structure, design patterns, error handling conventions, and testing strategies in a single context file. AI assistants reference this before generating any code, producing output that matches your team's standards from the first line.

The difference between an AI assistant that helps and one that creates rework is context. Give yours the codebase understanding it needs to be genuinely useful.

CodeAI Context Best Practices

Structure your codebase knowledge so AI coding assistants generate code that matches your architecture, patterns, and conventions from the first attempt.

Map Your File Structure

Document your project's directory layout with purpose annotations. AI assistants that understand where code lives and why it is organized that way generate files in the right locations with correct import paths.

Codify Naming Conventions

Spell out your naming patterns - file names, function names, CSS classes, database columns. Consistent naming is the single most visible signal of AI-generated code quality. Document the rules and AI follows them.

Document Error Patterns

Capture how your codebase handles errors - custom error classes, logging conventions, user-facing messages, retry logic. AI assistants that understand your error handling produce code that fails gracefully within your existing patterns.

Specify Testing Standards

Document your testing approach - unit test structure, integration test patterns, mocking conventions, assertion styles. AI-generated tests that follow your patterns are tests your team actually trusts and maintains.

Describe Integration Points

Map how components connect - API contracts, event systems, shared state, database access patterns. AI assistants that understand your integration architecture generate code that plugs in correctly the first time.

List Anti-Patterns

Document what not to do. Banned libraries, deprecated patterns, known pitfalls, and legacy approaches that should not be replicated. Negative constraints are as valuable as positive guidelines for AI code generation.

Include Working Examples

For each pattern, include a real code example from your codebase. AI assistants learn more from one concrete example than ten paragraphs of description. Show, do not just tell.

Version Your Context

Update your CodeAI context file when patterns change. A context file that describes last quarter's architecture actively misleads AI assistants. Keep it current or mark sections with last-verified dates.

Context Beats Prompting

The developers getting the best results from AI coding assistants are not writing longer prompts - they are writing better context files. A well-structured CodeAI.md file that describes your architecture, patterns, and conventions once gives every AI interaction a head start. Stop repeating yourself in prompts. Invest in context infrastructure that pays dividends on every task.

The CodeAI Template

CodeAI.md
# CodeAI.md - AI Coding Assistant Context
<!-- Context file optimized for AI coding assistants (Claude Code, Cursor, Copilot) -->
<!-- Structure maximizes AI effectiveness: codebase map, patterns, conventions, gotchas -->
<!-- Last updated: YYYY-MM-DD -->

## Codebase Overview

**Project**: Vaultline - Secure Document Management Platform
**Language**: TypeScript (strict mode)
**Framework**: Next.js 14 (App Router) with tRPC
**Database**: PostgreSQL 16 via Drizzle ORM
**Auth**: NextAuth.js v5 with Google and email/password providers

### High-Level Architecture
```
vaultline/
├── src/
│   ├── app/                    # Next.js App Router pages
│   │   ├── (auth)/             # Auth route group (login, register, reset)
│   │   ├── (dashboard)/        # Authenticated route group
│   │   │   ├── documents/      # Document listing, search, filters
│   │   │   ├── folders/        # Folder management and navigation
│   │   │   ├── settings/       # User and org settings
│   │   │   └── layout.tsx      # Dashboard shell with sidebar
│   │   ├── api/                # API routes
│   │   │   └── trpc/           # tRPC endpoint handler
│   │   ├── layout.tsx          # Root layout (providers, fonts, metadata)
│   │   └── page.tsx            # Landing page (marketing)
│   ├── components/
│   │   ├── ui/                 # Shadcn/ui primitives (button, dialog, input)
│   │   ├── documents/          # Document-specific components
│   │   ├── folders/            # Folder tree and navigation
│   │   └── shared/             # Cross-feature components (search, empty states)
│   ├── server/
│   │   ├── db/                 # Drizzle schema, migrations, seed
│   │   │   ├── schema.ts       # All table definitions
│   │   │   ├── migrations/     # SQL migration files
│   │   │   └── index.ts        # DB connection singleton
│   │   ├── trpc/               # tRPC router definitions
│   │   │   ├── routers/        # Feature-specific routers
│   │   │   ├── context.ts      # Request context (session, db)
│   │   │   └── index.ts        # Root router + type exports
│   │   └── services/           # Business logic (no framework deps)
│   │       ├── document.service.ts
│   │       ├── folder.service.ts
│   │       ├── permission.service.ts
│   │       └── storage.service.ts
│   ├── lib/                    # Shared utilities
│   │   ├── utils.ts            # General helpers (cn, formatDate, etc.)
│   │   ├── constants.ts        # App-wide constants
│   │   └── validators.ts       # Zod schemas for shared validation
│   ├── hooks/                  # Custom React hooks
│   └── types/                  # Shared TypeScript types
├── tests/
│   ├── unit/                   # Vitest unit tests
│   ├── integration/            # tRPC router integration tests
│   └── e2e/                    # Playwright end-to-end tests
├── drizzle.config.ts           # Drizzle ORM configuration
├── next.config.ts              # Next.js configuration
└── tailwind.config.ts          # Tailwind configuration
```

### Key Entry Points
- **App Shell**: `src/app/(dashboard)/layout.tsx` - sidebar, header, auth guard
- **tRPC Root Router**: `src/server/trpc/index.ts` - all API procedures
- **Database Schema**: `src/server/db/schema.ts` - all Drizzle table definitions
- **Auth Config**: `src/lib/auth.ts` - NextAuth providers, callbacks, session strategy

## Coding Conventions

### TypeScript Rules (Enforced by ESLint)
```typescript
// Strict mode is ON - no implicit any, no unused variables
// Use 'type' imports for type-only imports
import type { Document } from '@/types';
import { formatDate } from '@/lib/utils';

// Prefer interfaces for object shapes, types for unions/intersections
interface DocumentListProps {
  folderId: string;
  sortBy: 'name' | 'created' | 'modified';
  onSelect: (doc: Document) => void;
}

// Always use explicit return types on exported functions
export function calculateStorageUsage(files: FileRecord[]): number {
  return files.reduce((total, file) => total + file.sizeBytes, 0);
}

// Use 'as const' for literal objects, not enums
export const DOCUMENT_STATUS = {
  DRAFT: 'draft',
  PUBLISHED: 'published',
  ARCHIVED: 'archived',
} as const;

// Error handling: use Result pattern for service layer
type Result<T> = { success: true; data: T } | { success: false; error: string };
```

### Naming Conventions
```typescript
// Files: kebab-case
// document-list.tsx, use-document-search.ts, storage.service.ts

// Components: PascalCase (match filename without extension)
export function DocumentList({ folderId }: DocumentListProps) { }

// Hooks: camelCase prefixed with 'use'
export function useDocumentSearch(query: string) { }

// Services: camelCase methods, PascalCase class (if class-based)
export async function getDocumentById(id: string): Promise<Result<Document>> { }

// Database columns: snake_case in schema, camelCase in TypeScript via Drizzle mapping
// Table: documents, Column: created_at -> TS: createdAt

// Constants: UPPER_SNAKE_CASE
export const MAX_FILE_SIZE_MB = 50;
export const ALLOWED_MIME_TYPES = ['application/pdf', 'image/png', 'image/jpeg'];

// Environment variables: UPPER_SNAKE_CASE with app prefix
// VAULTLINE_DATABASE_URL, VAULTLINE_S3_BUCKET
```

### Component Patterns
```typescript
// Server Components are the default (no 'use client' directive)
// Only add 'use client' when you need hooks, event handlers, or browser APIs

// Server Component (default) - data fetching at the component level
export default async function DocumentsPage({ params }: { params: { folderId: string } }) {
  const documents = await trpc.document.listByFolder({ folderId: params.folderId });
  return <DocumentList documents={documents} />;
}

// Client Component - interactive UI
'use client';
export function DocumentSearch({ onResultClick }: DocumentSearchProps) {
  const [query, setQuery] = useState('');
  const results = trpc.document.search.useQuery({ query }, { enabled: query.length > 2 });
  // ...
}

// Composition: Server components pass data down, client components handle interaction
// Never fetch data in client components if you can pass it from a server component
```

## Patterns to Follow

### tRPC Router Pattern
```typescript
// All routers follow this structure
// File: src/server/trpc/routers/document.ts
import { z } from 'zod';
import { protectedProcedure, router } from '../trpc';
import * as documentService from '@/server/services/document.service';

export const documentRouter = router({
  getById: protectedProcedure
    .input(z.object({ id: z.string().uuid() }))
    .query(async ({ input, ctx }) => {
      // Always check permissions before returning data
      const doc = await documentService.getById(input.id);
      if (!doc) throw new TRPCError({ code: 'NOT_FOUND' });
      await ctx.permissions.assertCanView(ctx.session.userId, doc);
      return doc;
    }),

  create: protectedProcedure
    .input(createDocumentSchema)  // Zod schema from validators.ts
    .mutation(async ({ input, ctx }) => {
      return documentService.create({
        ...input,
        ownerId: ctx.session.userId,
      });
    }),
});
```

### Service Layer Pattern
```typescript
// Services contain business logic - no HTTP/tRPC/framework dependencies
// File: src/server/services/document.service.ts
import { db } from '@/server/db';
import { documents } from '@/server/db/schema';
import { eq, and, desc } from 'drizzle-orm';

export async function getById(id: string): Promise<Document | null> {
  const [doc] = await db.select().from(documents).where(eq(documents.id, id)).limit(1);
  return doc ?? null;
}

export async function create(data: CreateDocumentInput): Promise<Document> {
  const [doc] = await db.insert(documents).values(data).returning();
  return doc;
}

// Keep services focused - one service per domain entity
// Cross-entity logic goes in a dedicated service (e.g., permission.service.ts)
```

### Error Handling Pattern
```typescript
// tRPC layer: throw TRPCError with appropriate code
throw new TRPCError({
  code: 'NOT_FOUND',
  message: 'Document not found',
});

// Service layer: return Result type, let the caller decide how to handle
export async function moveDocument(docId: string, targetFolderId: string): Promise<Result<Document>> {
  const folder = await folderService.getById(targetFolderId);
  if (!folder) return { success: false, error: 'Target folder not found' };

  const [updated] = await db.update(documents)
    .set({ folderId: targetFolderId, updatedAt: new Date() })
    .where(eq(documents.id, docId))
    .returning();

  return { success: true, data: updated };
}

// Client components: use error boundaries and toast notifications
// Never show raw error messages to users - map to user-friendly messages
```

## Anti-Patterns to Avoid

```typescript
// DO NOT: Fetch data in client components when a server component could do it
// Bad:
'use client';
export function DocumentList() {
  const { data } = trpc.document.list.useQuery();  // Unnecessary client fetch
  return <ul>{data?.map(d => <li key={d.id}>{d.name}</li>)}</ul>;
}
// Good: Fetch in parent server component, pass as props

// DO NOT: Put business logic in tRPC routers
// Bad: Complex validation, data transformation, or multi-step operations in router
// Good: Thin routers that delegate to services

// DO NOT: Use raw SQL strings
// Bad: db.execute(sql`SELECT * FROM documents WHERE id = ${id}`)
// Good: Use Drizzle query builder for type safety

// DO NOT: Skip permission checks - every read and write must verify access
// Bad: return documentService.getById(input.id);
// Good: const doc = await documentService.getById(input.id);
//       await ctx.permissions.assertCanView(ctx.session.userId, doc);

// DO NOT: Import server-only code in client components
// Bad: import { db } from '@/server/db'; // in a 'use client' file
// Good: Use tRPC hooks for client-to-server communication
```

## Testing Strategy

### Test Structure
```
tests/
├── unit/                      # Fast, isolated tests
│   ├── services/              # Service function tests with mocked DB
│   ├── utils/                 # Utility function tests
│   └── validators/            # Zod schema validation tests
├── integration/               # Tests with real database
│   ├── routers/               # tRPC router tests via supertest
│   └── services/              # Service tests against test database
└── e2e/                       # Browser tests
    ├── auth.spec.ts           # Login, register, password reset
    ├── documents.spec.ts      # CRUD operations, search, filters
    └── sharing.spec.ts        # Permission sharing flows
```

### Running Tests
```bash
npm test                    # Run all unit tests (Vitest)
npm run test:integration    # Integration tests (needs running Postgres)
npm run test:e2e            # Playwright end-to-end tests
npm run test:coverage       # Unit test coverage report
```

### Testing Patterns
```typescript
// Unit test - mock the database layer
import { describe, it, expect, vi } from 'vitest';
import * as documentService from '@/server/services/document.service';

vi.mock('@/server/db', () => ({
  db: { select: vi.fn(), insert: vi.fn(), update: vi.fn(), delete: vi.fn() },
}));

describe('documentService.create', () => {
  it('should create a document with default status', async () => {
    // Arrange, Act, Assert
  });

  it('should reject files exceeding size limit', async () => {
    // Test validation logic
  });
});

// E2E test - use Playwright
import { test, expect } from '@playwright/test';

test('user can upload and view a document', async ({ page }) => {
  await page.goto('/documents');
  await page.getByRole('button', { name: 'Upload' }).click();
  // Test the full user flow
});
```

## AI Assistant Guidelines

### When Writing New Code
- Follow the patterns in this file exactly - check similar existing files first
- Use Drizzle ORM for all database operations, never raw SQL
- Add Zod validation schemas for all tRPC inputs in `src/lib/validators.ts`
- Server Components by default - only add 'use client' when necessary
- Always add permission checks in tRPC routers before returning data

### When Refactoring
- Run `npm run type-check` after any changes - zero TypeScript errors allowed
- Preserve existing test coverage - update tests if behavior changes
- Keep the service layer framework-agnostic (no Next.js or tRPC imports)
- If splitting a file, update all import paths across the codebase

### Common Gotchas for AI
- **Drizzle vs Prisma**: This project uses Drizzle, not Prisma. The APIs are different.
- **App Router vs Pages Router**: This project uses Next.js App Router. No `getServerSideProps`, no `pages/` directory. Use `async` server components for data fetching.
- **tRPC v11**: We use tRPC v11 with the `@trpc/react-query` integration. The hook API is `trpc.router.procedure.useQuery()`, not `trpc.useQuery()`.
- **Shadcn/ui**: Components are in `src/components/ui/` and are copied into the project (not a package). Modify them directly if needed.
- **File uploads**: Go through the `storage.service.ts` which handles S3 presigned URLs. Never accept file uploads directly in tRPC procedures.

### Questions to Ask Before Implementing
- "Is there an existing service function I should reuse?"
- "Does this need a permission check? What access levels are involved?"
- "Should this be a server component or client component?"
- "What Zod schema validates this input?"
- "Are there similar patterns in the codebase I should follow?"

Why Markdown Matters for AI-Native Development

Codebase as Context

AI coding assistants need structure, not chaos. CodeAI.md transforms your repository into a queryable knowledge graph. Document patterns, conventions, and integration points in markdown. Your AI pair programmer performs best when it understands not just what your code does, but why it exists and how it fits together.

Patterns over Prompts

Stop re-explaining your architecture in every prompt. CodeAI.md captures reusable patterns, common workflows, and decision frameworks in structured markdown. Feed your AI assistant context once, reference it forever. Consistency scales. Ad-hoc prompting doesn't.

Documentation that Executes

Your codebase documentation should drive code generation, not just describe it. CodeAI.md bridges the gap between human intent and machine implementation. Document your patterns in markdown, let AI assistants generate consistent, context-aware code. The docs become executable specifications.

"Elite development teams have discovered a secret: the best AI coding assistant is one that deeply understands your specific codebase. CodeAI.md gives you the infrastructure to capture and version that understanding, turning tribal knowledge into permanent competitive advantage."

Explore More Templates

About CodeAI.md

Our Mission

Built by engineers who understand that AI coding assistants are only as good as the context you provide them.

We believe the future of coding is collaborative - humans architect systems, AI generates implementation. But this only works when your AI assistant deeply understands your codebase. CodeAI.md helps you structure that understanding in markdown files that live alongside your code, evolve with your patterns, and compound in value over time.

Our mission is to help development teams capture their codebase knowledge in a format that is both human-readable and AI-optimal. When your patterns, conventions, and architectural decisions are documented in versioned .md files, every developer and every AI assistant benefits. This is how elite teams scale their expertise.

Why Markdown Matters

AI-Native

LLMs parse markdown better than any other format. Fewer tokens, cleaner structure, better results.

Version Control

Context evolves with code. Git tracks changes, PRs enable review, history preserves decisions.

Human Readable

No special tools needed. Plain text that works everywhere. Documentation humans actually read.

Questions about structuring your codebase for AI? Need help with implementation patterns? Reach out - we're here to help.