···11-Project:
22-You are a distinguished developer helping build Coves, a forum like atProto social media platform (think reddit / lemmy).
11+# CLAUDE-BUILD.md
22+33+Project: Coves Builder You are a distinguished developer actively building Coves, a forum-like atProto social media platform. Your goal is to ship working features quickly while maintaining quality and security.
44+55+## Builder Mindset
66+77+- Ship working code today, refactor tomorrow
88+- Security is built-in, not bolted-on
99+- Test-driven: write the test, then make it pass
1010+- When stuck, check Context7 for patterns and examples
1111+- ASK QUESTIONS if you need context surrounding the product DONT ASSUME
31244-Human & LLM Readability Guidelines:
55-- Clear Module Boundaries: Each feature is a self-contained module with explicit interfaces
1313+#### Human & LLM Readability Guidelines:
614- Descriptive Naming: Use full words over abbreviations (e.g., CommunityGovernance not CommGov)
77-- Structured Documentation: Each module includes purpose, dependencies, and example usage
88-- Consistent Patterns: RESTful APIs, standard error handling, predictable data structures
99-- Context-Rich Comments: Explain "why" not just "what" at decision points
1515+1616+## Build Process
1717+1818+### Phase 1: Planning (Before Writing Code)
1919+2020+**ALWAYS START WITH:**
2121+2222+- [ ] Identify which atProto patterns apply (check ATPROTO_GUIDE.md or context7 https://context7.com/bluesky-social/atproto)
2323+- [ ] Check if Indigo (also in context7) packages already solve this: https://context7.com/bluesky-social/indigo
2424+- [ ] Define the XRPC interface first
2525+- [ ] Write the Lexicon schema
2626+- [ ] Plan the data flow: CAR store → AppView
2727+ - [ ] - Follow the two-database pattern: Repository (CAR files)(PostgreSQL for metadata) and AppView (PostgreSQL)
2828+- [ ] **Identify auth requirements and data sensitivity**
2929+3030+### Phase 2: Test-First Implementation
3131+3232+**BUILD ORDER:**
3333+3434+1. **Domain Model** (`core/[domain]/[domain].go`)
3535+3636+ - Start with the simplest struct
3737+ - Add validation methods
3838+ - Define error types
3939+ - **Add input validation from the start**
4040+2. **Repository Interfaces** (`core/[domain]/repository.go`)
4141+4242+ ```go
4343+ type CommunityWriteRepository interface {
4444+ Create(ctx context.Context, community *Community) error
4545+ Update(ctx context.Context, community *Community) error
4646+ }
4747+4848+ type CommunityReadRepository interface {
4949+ GetByID(ctx context.Context, id string) (*Community, error)
5050+ List(ctx context.Context, limit, offset int) ([]*Community, error)
5151+ }
5252+ ```
5353+5454+3. **Service Tests** (`core/[domain]/service_test.go`)
10551111-Core Principles:
1212-- When in doubt, choose the simpler implementation
1313-- Features are the enemy of shipping
1414-- A working tool today beats a perfect tool tomorrow
5656+ - Write failing tests for happy path
5757+ - **Add tests for invalid inputs**
5858+ - **Add tests for unauthorized access**
5959+ - Mock repositories
6060+4. **Service Implementation** (`core/[domain]/service.go`)
15611616-Utilize existing tech stack
1717-- Before attempting to use an external tool, ensure it cannot be done via the current stack:
1818-- Go Chi (Web framework)
1919-- DB: PostgreSQL
2020-- atProto for federation & user identities
6262+ - Implement to pass tests
6363+ - **Validate all inputs before processing**
6464+ - **Check permissions before operations**
6565+ - Handle transactions
6666+5. **Repository Implementations**
21672222-## atProto Guidelines
6868+ - **Always use parameterized queries**
6969+ - **Never concatenate user input into queries**
7070+ - Write repo: `internal/atproto/carstore/[domain]_write_repo.go`
7171+ - Read repo: `db/appview/[domain]_read_repo.go`
7272+6. **XRPC Handler** (`xrpc/handlers/[domain]_handler.go`)
23732424-For comprehensive AT Protocol implementation details, see [ATPROTO_GUIDE.md](./ATPROTO_GUIDE.md).
7474+ - **Verify auth tokens/DIDs**
7575+ - Parse XRPC request
7676+ - Call service
7777+ - **Sanitize errors before responding**
25782626-Key principles:
2727-- Utilize Bluesky's Indigo packages before building custom atProto functionality
2828-- Everything is XRPC - no separate REST API layer needed
2929-- Follow the two-database pattern: Repository (CAR files) and AppView (PostgreSQL)
3030-- Design for federation and data portability from the start
7979+### Phase 3: Integration
31803232-# Architecture Guidelines
8181+**WIRE IT UP:**
33823434-## Required Layered Architecture
3535-Follow this strict separation of concerns:
3636-```
3737-Handler (XRPC) → Service (Business Logic) → Repository (Data Access) → Database
3838-```
3939-- Handlers: XRPC request/response only
4040-- Services: Business logic, uses both write/read repos
4141-- Write Repos: CAR store operations
4242-- Read Repos: AppView queries
8383+- [ ] Add to dependency injection in main.go
8484+- [ ] Register XRPC routes with proper auth middleware
8585+- [ ] Create migration if needed
8686+- [ ] Write integration test including auth flows
43878888+## Security-First Building
44894545-## Directory Structure
9090+### Every Feature MUST:
46914747-For a detailed project structure with file-level details and implementation status, see [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md).
9292+- [ ] **Validate all inputs** at the handler level
9393+- [ ] **Use parameterized queries** (never string concatenation)
9494+- [ ] **Check authorization** before any operation
9595+- [ ] **Limit resource access** (pagination, rate limits)
9696+- [ ] **Log security events** (failed auth, invalid inputs)
9797+- [ ] **Never log sensitive data** (passwords, tokens, PII)
48984949-The project follows a layered architecture with clear separation between:
5050-- **XRPC handlers** - atProto API layer
5151- - Only handle XRPC concerns: parsing requests, formatting responses
5252- - Delegate all business logic to services
5353- - No direct database access
5454-- **Core business logic** - Domain services and models
5555- - Contains all business logic
5656- - Orchestrates between write and read repositories
5757- - Manages transactions and complex operations
5858-- **Data repositories** - Split between CAR store writes and AppView reads
5959- - **Write Repositories** (`internal/atproto/carstore/*_write_repo.go`)
6060- - Modify CAR files (source of truth)
6161-- **Read Repositories** (`db/appview/*_read_repo.go`)
6262- - Query denormalized PostgreSQL tables
6363- - Optimized for performance
9999+### Red Flags to Avoid:
641006565-## Strict Prohibitions
6666-- **NEVER** put SQL queries in handlers
6767-- **NEVER** import database packages in handlers
6868-- **NEVER** pass *sql.DB directly to handlers
6969-- **NEVER** mix business logic with XRPC concerns
7070-- **NEVER** bypass the service layer
101101+- `fmt.Sprintf` in SQL queries → Use parameterized queries
102102+- Missing `context.Context` → Need it for timeouts/cancellation
103103+- No input validation → Add it immediately
104104+- Error messages with internal details → Wrap errors properly
105105+- Unbounded queries → Add limits/pagination
711067272-## Testing Requirements
7373-- Services must be easily mockable (use interfaces)
7474-- Integration tests should test the full stack
7575-- Unit tests should test individual layers in isolation
107107+## Quick Decision Guide
761087777-Test File Naming:
7878-- Unit tests: `[file]_test.go` in same directory
7979-- Integration tests: `[feature]_integration_test.go` in tests/ directory
109109+### "Should I use X?"
801108181-## Claude Code Instructions
111111+1. Does Indigo have it? → Use it
112112+2. Can PostgreSQL + Go do it securely? → Build it simple
113113+3. Requires external dependency? → Check Context7 first
821148383-### Code Generation Patterns
8484-When creating new features:
8585-1. Generate interface first in core/[domain]/
8686-2. Generate test file with failing tests
8787-3. Generate implementation to pass tests
8888-4. Generate handler with tests
8989-5. Update routes in xrpc/routes/
115115+### "How should I structure this?"
901169191-### Refactoring Checklist
9292-Before considering a feature complete:
9393-- All tests pass
9494-- No SQL in handlers
9595-- Services use interfaces only
9696-- Error handling follows patterns
9797-- API documented with examples
117117+1. One domain, one package
118118+2. Interfaces for testability
119119+3. Services coordinate repos
120120+4. Handlers only handle XRPC
981219999-## Database Migrations
100100-- Use golang-goose for version control
101101-- Migrations in db/migrations/
102102-- Never modify existing migrations
103103-- Always provide rollback migrations
122122+## Pre-Production Advantages
104123105105-## Dependency Injection
106106-- Use constructor functions for all components
107107-- Pass interfaces, not concrete types
108108-- Wire dependencies in main.go or cmd/server/main.go
124124+Since we're pre-production:
109125110110-Example dependency wiring:
111111-```go
112112-// main.go
113113-userWriteRepo := carstore.NewUserWriteRepository(carStore)
114114-userReadRepo := appview.NewUserReadRepository(db)
115115-userService := users.NewUserService(userWriteRepo, userReadRepo)
116116-userHandler := xrpc.NewUserHandler(userService)
117117-```
126126+- **Break things**: Delete and rebuild rather than complex migrations
127127+- **Experiment**: Try approaches, keep what works
128128+- **Simplify**: Remove unused code aggressively
129129+- **But never compromise security basics**
118130119119-## Error Handling
120120-- Define custom error types in core/errors/
121121-- Use error wrapping with context: fmt.Errorf("service: %w", err)
122122-- Services return domain errors, handlers translate to HTTP status codes
123123-- Never expose internal error details in API responses
131131+## Success Metrics
124132125125-### Context7 Usage Guidelines:
126126-- Always check Context7 for best practices before implementing external integrations and packages
127127-- Use Context7 to understand proper error handling patterns for specific libraries
128128-- Reference Context7 for testing patterns with external dependencies
129129-- Consult Context7 for proper configuration patterns
133133+Your code is ready when:
130134131131-## XRPC Implementation
135135+- [ ] Tests pass (including security tests)
136136+- [ ] Follows atProto patterns
137137+- [ ] No security checklist items missed
138138+- [ ] Handles errors gracefully
139139+- [ ] Works end-to-end with auth
132140133133-For detailed XRPC patterns and Lexicon examples, see [ATPROTO_GUIDE.md](./ATPROTO_GUIDE.md#xrpc).
141141+## Quick Checks Before Committing
134142135135-### Key Points
136136-- All client interactions go through XRPC endpoints
137137-- Handlers validate against Lexicon schemas automatically
138138-- Queries are read-only, procedures modify repositories
139139-- Every endpoint must have a corresponding Lexicon definition
143143+1. **Will it work?** (Integration test proves it)
144144+2. 1. **Is it secure?** (Auth, validation, parameterized queries)
145145+3. **Is it simple?** (Could you explain to a junior?)
146146+4. **Is it complete?** (Test, implementation, documentation)
140147141141-Key note: we are pre-production, we do not need migration strategies, feel free to tear down and rebuild, however ensure to erase any unneeded data structures or code.148148+Remember: We're building a working product. Perfect is the enemy of shipped.
+39-2
cmd/validate-lexicon/main.go
···11package main
2233import (
44+ "bytes"
45 "encoding/json"
56 "flag"
67 "fmt"
···270271 return nil
271272 }
272273273273- // Parse JSON data
274274+ // Parse JSON data using Decoder to handle numbers properly
274275 var recordData map[string]interface{}
275275- if err := json.Unmarshal(data, &recordData); err != nil {
276276+ decoder := json.NewDecoder(bytes.NewReader(data))
277277+ decoder.UseNumber() // This preserves numbers as json.Number instead of float64
278278+ if err := decoder.Decode(&recordData); err != nil {
276279 validationErrors = append(validationErrors, fmt.Sprintf("Failed to parse JSON in %s: %v", path, err))
277280 return nil
278281 }
282282+283283+ // Convert json.Number values to appropriate types
284284+ recordData = convertNumbers(recordData).(map[string]interface{})
279285280286 // Extract $type field
281287 recordType, ok := recordData["$type"].(string)
···438444439445 return nil
440446}
447447+448448+// convertNumbers recursively converts json.Number values to int64 or float64
449449+func convertNumbers(v interface{}) interface{} {
450450+ switch vv := v.(type) {
451451+ case map[string]interface{}:
452452+ result := make(map[string]interface{})
453453+ for k, val := range vv {
454454+ result[k] = convertNumbers(val)
455455+ }
456456+ return result
457457+ case []interface{}:
458458+ result := make([]interface{}, len(vv))
459459+ for i, val := range vv {
460460+ result[i] = convertNumbers(val)
461461+ }
462462+ return result
463463+ case json.Number:
464464+ // Try to convert to int64 first
465465+ if i, err := vv.Int64(); err == nil {
466466+ return i
467467+ }
468468+ // If that fails, convert to float64
469469+ if f, err := vv.Float64(); err == nil {
470470+ return f
471471+ }
472472+ // If both fail, return as string
473473+ return vv.String()
474474+ default:
475475+ return v
476476+ }
477477+}
···11+{
22+ "$type": "social.coves.community.wiki",
33+ "slug": "this-slug-is-way-too-long-and-exceeds-the-maximum-allowed-length-of-128-characters-which-should-trigger-a-validation-error-when-we-run-the-test",
44+ "title": "Invalid Wiki Page",
55+ "content": "This wiki page has a slug that exceeds the maximum length.",
66+ "createdAt": "2024-01-01T00:00:00Z"
77+}
+13
tests/lexicon-test-data/community/wiki-valid.json
···11+{
22+ "$type": "social.coves.community.wiki",
33+ "slug": "getting-started",
44+ "title": "Getting Started with Our Community",
55+ "content": "# Welcome to the Programming Community\n\nThis guide will help you get started with our community.\n\n## Rules\nPlease read our community rules before posting.\n\n## Resources\n- [FAQ](/wiki/faq)\n- [Posting Guidelines](/wiki/posting-guidelines)\n- [Code of Conduct](/wiki/code-of-conduct)",
66+ "author": "did:plc:moderator123",
77+ "editors": ["did:plc:editor1", "did:plc:editor2"],
88+ "isIndex": false,
99+ "createdAt": "2024-01-01T00:00:00Z",
1010+ "updatedAt": "2025-01-09T15:00:00Z",
1111+ "revision": 5,
1212+ "tags": ["meta", "help", "guide"]
1313+}
···11+{
22+ "$type": "social.coves.moderation.ruleProposal",
33+ "community": "did:plc:programmingcommunity",
44+ "proposalType": "addRule",
55+ "title": "No AI-generated content without disclosure",
66+ "description": "All AI-generated code or content must be clearly marked as such. This helps maintain transparency and allows community members to make informed decisions about the content they consume.",
77+ "proposalData": {
88+ "ruleTitle": "Disclose AI-generated content",
99+ "ruleDescription": "All posts containing AI-generated code or content must include a clear disclosure statement"
1010+ },
1111+ "requiredVotes": 100,
1212+ "createdAt": "2025-01-09T17:00:00Z"
1313+}
···11+{
22+ "$type": "social.coves.moderation.tribunalVote",
33+ "tribunal": "at://did:plc:community123/social.coves.moderation.tribunal/3k7a3dmb5bk2c",
44+ "subject": "at://did:plc:spammer123/social.coves.post.record/3k7a2clb4bj2b",
55+ "decision": "remove",
66+ "reasoning": "The moderator's action was justified based on clear violation of Rule 2 (No Spam). The user posted the same promotional content across multiple communities within a short timeframe.",
77+ "precedents": [
88+ "at://did:plc:community123/social.coves.moderation.case/3k6z2cla4aj1a",
99+ "at://did:plc:community456/social.coves.moderation.case/3k6y1bkz3zi0z"
1010+ ],
1111+ "dissenting": false,
1212+ "createdAt": "2025-01-09T18:00:00Z"
1313+}