Core principles, standards, and heuristics for AI-assisted development
Last Updated: 2025-10-30
Version: 3.0
- Core Principles
- Context Management
- Documentation Management
- File and Directory Naming
- Code Quality Heuristics
- Security Heuristics
- Testing Strategy
- Language and Style
- Tech Stack Defaults
- Development Workflow
Context is a finite resource. Every token in the context window reduces the model's attention budget.
Token Economics (2025):
- Cached tokens: $0.30 per million tokens (cheap)
- Uncached tokens: $3.00 per million tokens (10x more expensive)
- Context window at 95%: Model performance degrades ("context rot")
Think of tokens as money: Would you spend $3 to read a 50,000 line file when grep can find the answer in one line for $0.001?
Apply this by:
- Loading only files you actively need to modify
- Using grep/search to find specific patterns before reading full files
- Checking git diff to see recent changes instead of re-reading entire files
- Keeping temporary notes in .ai/notes/ and cleaning them regularly
- Using file system as external memory (unlimited, persistent, directly operable)
Example workflow:
# Bad: Load everything ($$$, slow, pollutes context)
cat apps/api/internal/**/*.go
# Good: Progressive exploration ($, fast, focused)
tree apps/api/internal -L 2 # Understand structure
grep -r "authentication" apps/api/ # Find relevant files
cat apps/api/internal/handlers/auth.go # Read specific file
# Even better: Use external memory
echo "Auth uses JWT with 1h expiration" > .ai/notes/auth-findings.md
# Later: cat .ai/notes/auth-findings.md (cheap, cached)Auto-compact strategy: When context reaches 95%, summarize trajectory and continue. Keep high-signal information, discard verbose details.
Don't pre-load all information. Let AI discover what it needs through exploration.
Pattern:
- Start with high-level structure
- Identify relevant areas
- Dive into specific files only when needed
- Use metadata (file names, directory structure) as navigation hints
Why this works:
- Mirrors human cognition (we use file systems, not memorize everything)
- Keeps context focused on current task
- Allows iterative refinement of understanding
Provide principles for decision-making, not step-by-step instructions.
Bad (algorithmic):
If table has > 1000 rows, use pagination
If table has > 10000 rows, add index
If query time > 500ms, optimize
Good (heuristic):
Performance Principle:
- Measure before optimizing
- Optimize bottlenecks (queries > 500ms, operations in loops)
- Add indexes for WHERE/JOIN/ORDER BY on frequently queried columns
- Consider pagination when users typically view subset of data
The heuristic gives AI the reasoning framework to apply to novel situations.
Most common AI failure mode: Bloated tool sets with overlapping or ambiguous functionality.
Golden rule: If a human engineer can't definitively say which tool to use in a given situation, an AI agent can't be expected to do better.
Bad (ambiguous tools):
createUser() // Creates user in database
addUser() // Also creates user?
registerUser() // Also creates user??
insertUser() // Wait, what's the difference?AI thinks: "Which one? They all create users... I'll try createUser()... or maybe registerUser() sounds more right?"
Good (clear, single-purpose):
createUser() // The only way to create a user
updateUser() // The only way to update
deleteUser() // The only way to deleteAI thinks: "Need to create user → createUser(). Done."
Apply this by:
- One tool per distinct operation
- Different tools should have clearly different purposes
- Tool names reveal intent (createUser vs getUserById - obviously different)
- If tools overlap, merge them or make distinction crystal clear
Examples of good separation:
// Clear purposes
fetchUserById(id) # Get one user
fetchUsersByRole(role) # Get filtered list
createUser(data) # Create new
updateUserEmail(id, email) # Update specific field
// Bad: Too similar
getUser() # Get how? By what?
retrieveUser() # Same as getUser?
loadUser() # Also same?For function design: Strong type signatures help AI understand constraints and implement correctly. Tests serve as living documentation of expected behavior.
- Keep full context of files being modified
- No special management needed
- After 3-4 significant tool calls, summarize findings in code comments
- Drop files that are no longer actively being modified
- Keep only current focus in context
Required: Create .ai/notes/work-log.md
# Work Log: [Feature Name]
## Session: 2025-10-30 14:30
**Goal**: Implement JWT-based authentication
**Progress**: Token generation complete, working on refresh mechanism
**Active Files**:
- internal/handlers/auth.go (modified)
- internal/services/auth.go (modified)
- migrations/003_refresh_tokens.up.sql (created)
**Key Decisions**:
- Using RS256 algorithm (public/private key pair)
- Access tokens: 1 hour lifetime
- Refresh tokens: 7 days, stored in Redis
- Token rotation on each refresh for security
**Blockers**: None
**Next**: Implement token refresh endpoint and middleware
## Session: 2025-10-29 10:00
[Previous session notes]Benefits:
- Survives context resets
- Clear audit trail of decisions
- Easy to resume after breaks
- Can be referenced in future sessions
Problem: AI agents often create many .md files (auth-notes.md, database-design.md, api-docs.md) which become stale and chaotic.
Solution: Use structured locations for different types of documentation.
GUIDE.md (this file):
- Development principles and standards
- Heuristics and best practices
- Does NOT change from project to project
- Static template
DOCS.md:
- Living project documentation
- Grows as features are implemented
- Single source of truth for implementation details
- Includes: architecture, API docs, deployment, troubleshooting
.ai/context.md:
- Business context (what, why, for whom)
- Product goals and success metrics
- Tech stack choices specific to this project
.claude.md (optional):
- Repository-specific rules and conventions
- Automatically loaded into Claude Code's context on startup
- Keep concise (50-100 lines max) and human-readable
When to create .claude.md:
- Project has unique conventions not covered in GUIDE.md
- Specific tools/ORMs used (e.g., "Use Prisma, not raw SQL")
- Non-obvious behaviors (e.g., "API always wraps responses in {data: ...}")
- Setup requirements (e.g., "Run
npm run seedbefore testing")
Example .claude.md:
# Project-Specific Rules
## Database
- Use Prisma ORM for all database operations
- Never write raw SQL queries
- Migrations in prisma/migrations/
## API Responses
- Always wrap in {data: ..., error: null} format
- Use status codes: 200 (success), 400 (validation), 500 (server error)
## Testing
- Run `npm run seed` to populate test database
- All tests must pass before committing
- Mock external API calls
## Forbidden
- Never modify files in /generated folder (auto-generated)
- Don't touch legacy/ folder (deprecated, will be removed)Why this works: Claude Code automatically pulls .claude.md into context. No need to repeat these rules in every conversation.
.ai/tasks.md:
- Current task queue
- Tasks with goals and success metrics
- Updated as work progresses
.ai/notes/:
- Temporary working notes during task implementation
- MUST be cleaned after task completion
- Not permanent documentation
Code comments:
- Explain "why" not "what"
- Complex algorithm explanations
- Non-obvious business logic
Create temporary note:
.ai/notes/auth-implementation.mdWork freely in this file:
# Auth Implementation - Working Notes
## Current Progress
- [x] User registration endpoint
- [x] JWT token generation
- [ ] Token refresh logic
## Exploration
Tried session-based auth first, but decided on JWT because:
- Stateless scaling
- Better for mobile apps
- Industry standard
## Implementation Details
[Code snippets, trials, experiments]
## Questions
- Should refresh tokens be in DB or Redis? -> Redis for speed- Extract important information to DOCS.md:
## Authentication
### Implementation
**Completed**: 2025-10-30
**Token Strategy**:
- Access tokens: RS256, 1 hour lifetime
- Refresh tokens: 7 days, stored in Redis
- Token rotation on each refresh
**Endpoints**:
- POST /api/auth/register
- POST /api/auth/login
- POST /api/auth/refresh
- POST /api/auth/logout
**Security Measures**:
- Rate limiting: 5 attempts per minute per IP
- Password hashing: bcrypt cost 12
- Token blacklist in Redis for logout
**Code References**:
- Handler: internal/handlers/auth.go
- Service: internal/services/auth.go
- Middleware: internal/middleware/auth.go- Delete temporary note:
rm .ai/notes/auth-implementation.md- Commit with reference:
feat: implement JWT-based authentication system
- user registration with email/password validation
- JWT access and refresh token generation
- token validation middleware for protected routes
- rate limiting on authentication endpoints
- refresh token rotation for enhanced security
Documentation: Updated DOCS.md#authentication
Need to document something?
|
├─> Still working on it?
| └─> .ai/notes/[task-name].md
|
├─> Implementation detail in code?
| └─> Code comments
|
├─> Architectural decision?
| └─> DOCS.md#architecture
|
├─> API endpoint?
| └─> DOCS.md#api-endpoints
|
├─> Deployment procedure?
| └─> DOCS.md#deployment
|
└─> Troubleshooting solution?
└─> DOCS.md#troubleshooting
- Important decisions documented in DOCS.md
- API endpoints documented (if added)
- Temporary notes moved or deleted from .ai/notes/
- Commit message references documentation updates
Consistent, predictable naming makes code navigation faster for both humans and AI
Files should have clear, single-purpose names using kebab-case.
Location: Root directory (always visible) Naming: UPPERCASE.md for framework docs, lowercase.md for project-specific
Root documentation (UPPERCASE):
GUIDE.md # Development principles (this file)
DOCS.md # Living documentation (implementation details)
README.md # Entry point, quick start
CHANGELOG.md # Version history
CONTRIBUTING.md # How to contribute
LICENSE # Legal terms
Why UPPERCASE:
- Immediately visible in file listings
- Distinguishes framework docs from code
- Standard practice (README, LICENSE, etc.)
Location: .ai/ directory (AI-specific context)
Naming: kebab-case for all context files
.ai/
├── context.md # Business context (what, why, for whom)
├── tasks.md # Task queue for autonomous development
└── notes/ # Temporary working notes
├── auth-flow.md # Example: working on authentication
└── api-design.md # Example: API design exploration
Naming rules:
- Use descriptive single words:
context.md,tasks.md - Multi-word: kebab-case (
product-strategy.md) - Temporary notes: descriptive + task-oriented (
auth-flow.md) - NO underscores, NO spaces, NO camelCase
Bad examples:
.ai/ProductStrategy.md # Don't use PascalCase
.ai/product_strategy.md # Don't use snake_case
.ai/PRODUCT_STRATEGY.md # Don't use UPPERCASE (reserved for root)
.ai/notes/my notes.md # Don't use spaces
Good examples:
.ai/context.md # Single word
.ai/product-strategy.md # Multi-word with kebab-case
.ai/notes/auth-redesign.md # Descriptive, task-focused
Location: spec/ directory (optional, for complex projects)
Naming: kebab-case, descriptive
spec/
├── api-design.md # API endpoints specification
├── database-schema.md # Database structure
├── product-strategy.md # Business strategy (moved from .ai/)
└── architecture.md # System architecture decisions
When to use spec/:
- Complex projects with extensive specifications
- When
.ai/context files get too large (> 500 lines) - Formal documentation that rarely changes
When to keep in .ai/:
- MVP or early-stage projects
- Living documents that change frequently
- AI agent working context
Naming: Always lowercase, descriptive
Go:
user.go, auth.go, handler.go # Good: clear purpose
util.go, helper.go, common.go # Bad: too generic
TypeScript/JavaScript:
user-service.ts # Good: multi-word kebab-case
UserService.ts # Also OK: PascalCase for classes
userService.ts # OK: camelCase if consistent
Follow language conventions:
- Go: lowercase with underscores for multi-word (user_service.go)
- TypeScript: PascalCase for classes, camelCase for utilities
- Python: snake_case (user_service.py)
Top-level directories: Always lowercase, single word or kebab-case
Good structure:
project/
├── apps/ # Applications
├── libs/ # Shared libraries
├── infra/ # Infrastructure
├── spec/ # Specifications (optional)
└── .ai/ # AI context
Bad structure:
project/
├── Apps/ # Don't capitalize
├── my_libs/ # Don't use snake_case
├── Spec Files/ # Don't use spaces
When refactoring existing projects:
- Don't break existing conventions without discussion
- New files follow these rules
- Gradual migration during major refactors
- Document exceptions in project README
Example migration commit:
refactor: standardize file naming to kebab-case
- Renamed PRODUCT_STRATEGY.md -> spec/product-strategy.md
- Moved complex specs from .ai/ to spec/
- Updated references in documentation
Part of Kit naming conventions adoption.
Good code reveals intent clearly.
Indicators of good code:
- Function names reveal purpose (createUser, not handle)
- Error messages explain what and why
- Variable names are descriptive (userData, not d)
- Types are specific (User, not map[string]interface{})
Indicators of bad code:
- Generic names (data, item, thing, temp)
- Silent failures (errors swallowed without logging)
- Magic numbers (use constants)
- Cryptic abbreviations (usr, tmp, val)
Example:
// Bad: implicit, unclear
func process(d map[string]interface{}) error {
v := d["email"]
if v == nil {
return errors.New("error")
}
// ...
}
// Good: explicit, clear
func createUser(userData CreateUserRequest) error {
if userData.Email == "" {
return fmt.Errorf("email is required for user creation")
}
// ...
}Apply validation and error handling at system boundaries. Trust internal code.
Boundaries (apply defensive programming):
- HTTP request handlers
- User input processing
- External API calls
- File I/O operations
- Database queries
Internal (trust typed parameters):
- Service layer with validated data
- Internal function calls
- Business logic with guaranteed invariants
Example:
// Boundary: HTTP handler - defensive
func (h *Handler) CreateUser(c *gin.Context) {
var req CreateUserRequest
// Validate request format
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "Invalid request format"})
return
}
// Validate business rules
if !isValidEmail(req.Email) {
c.JSON(400, gin.H{"error": "Invalid email address"})
return
}
if len(req.Password) < 8 {
c.JSON(400, gin.H{"error": "Password must be at least 8 characters"})
return
}
// Pass validated data to service
user, err := h.service.CreateUser(c.Request.Context(), req)
if err != nil {
log.Error("Failed to create user", "error", err)
c.JSON(500, gin.H{"error": "Internal server error"})
return
}
c.JSON(201, gin.H{"user": user})
}
// Internal: Service layer - trusting
func (s *Service) CreateUser(ctx context.Context, req CreateUserRequest) (*User, error) {
// No re-validation needed - handler already validated
// Focus on business logic
hashedPassword, err := bcrypt.GenerateFromPassword(
[]byte(req.Password),
bcrypt.DefaultCost,
)
if err != nil {
return nil, fmt.Errorf("failed to hash password: %w", err)
}
user := &User{
ID: uuid.New().String(),
Email: req.Email,
PasswordHash: string(hashedPassword),
CreatedAt: time.Now(),
}
if err := s.repo.Create(ctx, user); err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
}
return user, nil
}Categorize errors and handle appropriately:
Transient (retry-able):
- Network timeouts
- Rate limit errors
- Temporary service unavailability
- Database connection issues
Strategy: Exponential backoff, max 3-5 retries
func callExternalAPI(ctx context.Context, url string) (*Response, error) {
var resp *Response
var err error
backoff := time.Second
maxRetries := 3
for i := 0; i < maxRetries; i++ {
resp, err = http.Get(url)
if err == nil && resp.StatusCode < 500 {
return resp, nil // Success or permanent error
}
// Transient error, retry with backoff
time.Sleep(backoff)
backoff *= 2
}
return nil, fmt.Errorf("failed after %d retries: %w", maxRetries, err)
}Permanent (fail fast):
- Validation errors
- Authentication failures
- Not found errors
- Bad request format
Strategy: Return clear error immediately
func (s *Service) GetUser(ctx context.Context, id string) (*User, error) {
if id == "" {
return nil, ErrInvalidUserID // Fail fast, don't hit database
}
user, err := s.repo.FindByID(ctx, id)
if err == sql.ErrNoRows {
return nil, ErrUserNotFound // Permanent error, clear message
}
if err != nil {
return nil, fmt.Errorf("database error: %w", err)
}
return user, nil
}Unknown (investigate):
- Unexpected 500 errors
- Database deadlocks
- Panics/crashes
Strategy: Log with context, alert team, return generic error to user
func (h *Handler) GetUser(c *gin.Context) {
user, err := h.service.GetUser(c.Request.Context(), c.Param("id"))
if err != nil {
// Log full error details for investigation
log.Error("Failed to get user",
"error", err,
"user_id", c.Param("id"),
"request_id", c.GetHeader("X-Request-ID"),
)
// Return generic error to user (don't leak internals)
c.JSON(500, gin.H{"error": "Internal server error"})
return
}
c.JSON(200, gin.H{"user": user})
}External sources (never trust):
- User input (forms, query parameters, headers)
- File uploads
- External API responses
- URL parameters
- Cookies
Internal sources (can trust after initial validation):
- Database records (validated on insert)
- Internal service calls
- System-generated data
At API Boundary:
func (h *Handler) CreatePost(c *gin.Context) {
var req CreatePostRequest
// 1. Validate format
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "Invalid JSON format"})
return
}
// 2. Validate business rules
if len(req.Title) < 3 || len(req.Title) > 100 {
c.JSON(400, gin.H{"error": "Title must be 3-100 characters"})
return
}
// 3. Sanitize inputs
req.Title = strings.TrimSpace(req.Title)
req.Content = sanitizeHTML(req.Content)
// 4. Check authorization
userID := c.GetString("user_id") // From auth middleware
if userID == "" {
c.JSON(401, gin.H{"error": "Unauthorized"})
return
}
// Now safe to pass to service
post, err := h.service.CreatePost(c.Request.Context(), userID, req)
// ...
}Default strategy:
- JWT for stateless authentication
- Short-lived access tokens (15-60 minutes)
- Long-lived refresh tokens (7-30 days)
- Refresh tokens stored server-side (Redis or database)
- Token rotation on each refresh
Implementation checklist:
- Passwords hashed with bcrypt (cost >= 12)
- JWT signed with strong algorithm (RS256 or HS256)
- Refresh tokens have unique identifiers
- Token blacklist for logout
- Rate limiting on auth endpoints (5 attempts/minute)
- HTTPS only in production
- Secure, HttpOnly cookies for tokens (if using cookies)
Always use parameterized queries, never string concatenation:
// WRONG - vulnerable to SQL injection
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)
db.Query(query)
// CORRECT - parameterized query
query := "SELECT * FROM users WHERE email = $1"
db.Query(query, email)Write tests for:
- Business-critical logic (authentication, payments, data integrity)
- Complex algorithms or calculations
- Code that integrates with external services
- Code that has failed in production before
- Public API endpoints
Skip tests for:
- Simple CRUD with no business logic
- Framework boilerplate (e.g., struct definitions)
- Code that changes daily (prototyping phase)
- Getters/setters with no logic
/\
/E2E\ 10% - Critical user flows
/------\
/ INT \ 20% - API + Database integration
/----------\
/ UNIT \ 70% - Business logic, algorithms
/--------------\
Unit Tests (70%):
- Test individual functions
- Mock external dependencies
- Fast execution (milliseconds)
- Focus on business logic
Integration Tests (20%):
- Test API endpoints with real database
- Verify data persistence
- Test error handling
- Slower (seconds)
E2E Tests (10%):
- Test critical user journeys
- Use real services when possible
- Slowest (minutes)
- Focus on happy paths and critical failures
HIGH Risk (money, auth, data loss):
- Unit tests + Integration tests + E2E tests
- Code coverage > 80%
- Manual security review
MEDIUM Risk (user features):
- Unit tests + Integration tests
- Code coverage > 60%
LOW Risk (UI, formatting):
- Manual testing acceptable
- Optional unit tests
// Pattern: TestFunctionName_Scenario_ExpectedBehavior
func TestCreateUser_ValidInput_ReturnsUser(t *testing.T) { }
func TestCreateUser_DuplicateEmail_ReturnsError(t *testing.T) { }
func TestCreateUser_InvalidEmail_ReturnsError(t *testing.T) { }All technical artifacts MUST be in English. Zero exceptions.
Applies to:
- Source code (variables, functions, classes, types)
- Code comments and documentation
- Commit messages (title and body)
- Pull request titles and descriptions
- Error messages and log output
- Database schema (tables, columns)
- API endpoints and parameters
- Configuration files
- Test descriptions
Exceptions (user-facing only):
- Frontend UI text for internationalization
- Email templates for customers
- Customer support responses
Why English Only:
- AI models trained primarily on English code
- Better token efficiency (non-English uses more tokens)
- Global collaboration standard
- Better tool integration
- Professional industry standard
Enforcement:
- Pre-commit hook blocks non-English commits
- CI pipeline fails on non-English content
- Code review includes language check
Examples:
// WRONG - using transliteration as example
func sozdatPolzovatelya(email string) error {
// proveryaem email (checking email)
if !isValid(email) {
return errors.New("neverni format") // wrong format
}
}
// CORRECT - English only
func createUser(email string) error {
// Validate email format before processing
if !isValid(email) {
return errors.New("invalid email format")
}
}Emoji are prohibited in all technical contexts.
Prohibited in:
- Source code (including comments)
- Commit messages
- Pull requests
- Log messages
- Error messages
- CLI output
- Technical documentation
Why No Emoji:
- Inconsistent rendering
- Not searchable (can't grep)
- Unprofessional
- Can break terminals/tools
- Wastes tokens
- No semantic value
Use instead:
- Heroicons (SVG) for frontend UI
- ASCII symbols for visual separation
- Clear text descriptions
Examples:
WRONG: feat: add auth [with lock emoji]
CORRECT: feat: implement authentication system
WRONG: log.Error("DB failed [with fire emoji]")
CORRECT: log.Error("Database connection failed")
<type>: <short description>
<detailed explanation of what and why>
- bullet point of specific change
- another specific change
- test coverage added
Technical notes:
- any important implementation details
- performance implications
- breaking changes
Types: feat, fix, docs, refactor, test, chore, perf
Example:
feat: implement JWT-based authentication system
Add complete authentication flow with JWT tokens for API security.
Addresses requirement for stateless authentication across multiple
instances in production environment.
- create user registration endpoint with email/password validation
- implement login endpoint with JWT access and refresh tokens
- add authentication middleware for protected routes
- implement password hashing using bcrypt with cost factor 12
- add refresh token mechanism with Redis storage for revocation
Technical notes:
- access tokens expire in 1 hour
- refresh tokens expire in 7 days with rotation
- using RS256 algorithm for JWT signing
- rate limiting: 5 login attempts per minute per IP
Breaking changes: none
Core Principle: All changes to master via Pull Requests
Never push directly to master. Even small changes go through PR review.
Why PR-based workflow:
- Human approval required (maintain control)
- Small, reviewable changes (easier to understand)
- Clear history (what changed and why)
- Rollback is simple (revert PR)
- Prevents accidental breaking changes
Branch naming:
feature/add-auth-system
fix/doctor-script-crash
docs/improve-quickstart
refactor/simplify-validation
chore/update-dependencies
PR size guidelines:
- Small: < 200 lines changed (ideal)
- Medium: 200-500 lines (acceptable)
- Large: > 500 lines (break it up)
One PR = One Thing:
Good: PR adds naming conventions to GUIDE.md
Bad: PR adds naming conventions + license + redesigns README
Split into 3 PRs:
1. Add naming conventions
2. Add AGPL license
3. Redesign README
Review process:
- AI creates branch + commits + pushes
- AI creates PR with clear description
- Human reviews and approves (required)
- Merge via GitHub UI
- Delete branch
For AI agents: After creating PR, STOP and wait for human approval. Do not merge automatically.
## What
Brief description of what this PR does.
## Why
Explanation of the problem this solves or feature it adds.
## Changes
- Specific change 1
- Specific change 2
- Specific change 3
## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests pass
- [ ] Manual testing completed
- [ ] Edge cases verified
## Checklist
- [ ] Code in English only
- [ ] No emoji in technical content
- [ ] Error handling implemented
- [ ] Input validation added
- [ ] Tests added/updated
- [ ] Documentation updated (DOCS.md)Default: Go
Use Go when building:
- REST APIs
- gRPC services
- Background workers
- Data collectors/processors
- ETL pipelines
Why Go:
- High performance
- Simple deployment (single binary)
- Strong typing
- Excellent concurrency (goroutines)
- Fast compilation
Project structure:
apps/api/
├── cmd/
│ └── main.go # Entry point
├── internal/
│ ├── handlers/ # HTTP handlers (thin)
│ ├── services/ # Business logic
│ ├── repository/ # Data access
│ ├── models/ # Data structures
│ └── middleware/ # HTTP middleware
├── migrations/ # Database migrations
└── tests/ # Tests
Default: TypeScript + React/Next.js
Why TypeScript:
- Mandatory for type safety
- Better IDE support
- Catches errors at compile time
Framework choice:
- Next.js: When you need SSR, SSG, or API routes
- React (Vite): When building SPA
Styling: Tailwind CSS
- Utility-first approach
- No custom CSS unless necessary
Icons: Heroicons
- SVG-based, not emoji
- Consistent design system
Project structure:
apps/ui/
├── src/
│ ├── app/ # Next.js app directory
│ ├── components/ # React components
│ │ ├── ui/ # Base UI (buttons, inputs)
│ │ └── features/ # Feature-specific
│ ├── lib/ # Utilities
│ │ ├── api.ts # API client
│ │ └── utils.ts
│ ├── hooks/ # Custom hooks
│ └── types/ # TypeScript types
└── public/ # Static assets
Default: PostgreSQL
Why PostgreSQL:
- Robust and reliable
- Excellent performance
- Rich feature set (JSON, full-text search)
- Strong ACID guarantees
Migrations: Use golang-migrate or similar
- All schema changes through migrations
- Never manual ALTER statements
- Migrations in version control
Default: Redis
Use cases:
- Session storage
- Rate limiting
- Caching frequently accessed data
- Pub/sub for real-time features
- Queue for background jobs
Development: Docker Compose Production: Kubernetes
Why this split:
- Docker Compose: Simple, fast local development
- Kubernetes: Production-grade orchestration, zero-downtime deployments
To use different tech stack, specify in .ai/context.md:
## Tech Stack Overrides
**Backend**: Node.js with Express
Reason: Team expertise, existing codebase compatibility
**Frontend**: Vue.js
Reason: Client requirement
**Database**: MongoDB
Reason: Document-based data model fits use case-
Understand Context
- Read .ai/tasks.md for current task
- Review acceptance criteria and success metrics
- Check dependencies (previous tasks completed?)
- Read relevant sections of DOCS.md
-
Plan Approach
- Identify files to modify
- Consider edge cases
- Think about error handling
- Plan test strategy
- Create work note if task > 2 hours
-
Implement Incrementally
- Start with core functionality
- Add error handling
- Add input validation
- Write tests alongside code
- Commit working code frequently (every 30-60 min)
-
Document
- Update DOCS.md with important decisions
- Clean up .ai/notes/ temporary files
- Add code comments for complex logic
-
Validate
- Run tests (make test)
- Run linter (make lint)
- Run doctor.sh for validation
- Manual testing
-
Update Progress
- Update task status in .ai/tasks.md
- Commit with clear message referencing docs
Ask immediately when:
- Requirements are ambiguous or contradictory
- Multiple valid approaches with different tradeoffs
- Security implications unclear
- Breaking changes might be needed
- Success metrics cannot be measured
Don't ask when:
- Standard patterns exist in codebase (check existing code first)
- Documentation already covers it (check GUIDE.md, DOCS.md)
- It's a well-known best practice
- You can make reasonable assumption and document it
Follow existing patterns:
- If codebase uses certain structure, maintain consistency
- Don't reinvent organization unless clearly better
- Document any new patterns in DOCS.md
Default patterns (if starting fresh):
- See Tech Stack Defaults section for structure
- Keep related code together (feature-based organization)
- Separate concerns (handlers, services, repository layers)
type ErrorResponse struct {
Error string `json:"error"` // User-facing message
Code string `json:"code,omitempty"` // Machine-readable error code
Details string `json:"details,omitempty"` // Additional context
}
// Usage
c.JSON(400, ErrorResponse{
Error: "Invalid email address",
Code: "INVALID_EMAIL",
Details: "Email must be in format user@domain.com",
})type SuccessResponse struct {
Data interface{} `json:"data"`
Meta *Meta `json:"meta,omitempty"` // Pagination, etc.
}
type Meta struct {
Total int `json:"total"`
Page int `json:"page"`
PerPage int `json:"per_page"`
TotalPages int `json:"total_pages"`
}// Initialize connection pool
db, err := sql.Open("postgres", connectionString)
if err != nil {
log.Fatal("Failed to connect to database", "error", err)
}
// Configure pool
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
// Verify connection
if err := db.Ping(); err != nil {
log.Fatal("Database is not reachable", "error", err)
}Version: 3.0
Last Updated: 2025-10-30
Maintained by: Development team
Questions: Check DOCS.md or ask in team chat