Latest commit

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

(See CLAUDE.md for sharper context-saving alternative)

Code Style Guide

Core Principle: Context is finite. Every token — code, comment, structure — competes for limited attention. Maximize signal, minimize noise. Write for two audiences: humans with limited working memory and AI agents with bounded context windows.

Philosophy

The optimal code is the minimum necessary to solve the problem correctly. Every additional line is debt.

Progressive Disclosure: Structure code layer-by-layer. Readers grasp high-level flow immediately, drilling into details only when needed. File names indicate purpose. Directory structures mirror conceptual hierarchies. Function names describe behavior without reading implementation. See Progressive Disclosure for concrete patterns.

Self-Documenting: Names eliminate need for comments. Comments explain "why," never "what." If you chose algorithm A over B for subtle reasons, state that. If you're working around a library bug, explain it.

Aggressive Minimalism: Before adding code, ask: "Is this the simplest solution?" Before adding a comment: "Does this clarify something non-obvious?" Before introducing an abstraction: "Does this reduce complexity, or merely relocate it?"

AHA Over DRY: Avoid Hasty Abstractions. Wait for the 3rd duplication before extracting. The wrong abstraction is worse than duplication. Three similar lines of code is better than a premature abstraction.

Progressive Disclosure

Structure every layer of your system so readers — human or agent — get the right level of detail at the right time. No one should need to read 2000 lines to understand what a module does.

The Zoom Principle

Code should work like a map: zoom out for the big picture, zoom in for street-level detail. Each zoom level should be self-sufficient.

// Level 0: Directory structure tells you what exists
src/
├── authentication/ # "There's an auth system"
├── orders/ # "There's an order system" ├── payments/ # "There's a payment system"
└── README.md # How they connect
// Level 1: Index file tells you what it can do
// authentication/index.ts
export { authenticateUser } from './authenticate';
export { refreshSession } from './sessions';
export { revokeAccess } from './revoke';
// No implementation visible — just capabilities
// Level 2: Function signature tells you the contract
async function authenticateUser(
credentials: UserCredentials,
db: Database,
clock: Clock
): Promise<Result<AuthSession, AuthError>>
// Level 3: Implementation tells you how
// Only read this when you need to change the behaviour

File-Level Disclosure

Every file should answer "what is this?" in its first 10 lines. Implementation details belong below.

// ✅ Top of file reveals purpose, contract, and shape/** * Order Processing Pipeline *  * Validates → enriches → prices → submits orders. * Entry point: processOrder() * Error strategy: Result types, no throws */// Types first — the contracttypeProcessOrderInput={/* ... */};typeProcessOrderResult=Result<Receipt,ProcessError>;// Public API secondexportasyncfunctionprocessOrder(input: ProcessOrderInput): Promise<ProcessOrderResult>{constvalidated=validateOrder(input);if(!validated.ok)returnvalidated;constenriched=awaitenrichWithInventory(validated.value);if(!enriched.ok)returnenriched;returnsubmitOrder(enriched.value);}// Private helpers last — only read if you need to understand a specific stepfunctionvalidateOrder(input: ProcessOrderInput): Result<ValidatedOrder,ProcessError>{// ...}
// ❌ Implementation soup — must read everything to understand anythingimport{db}from'../globals';constRETRY_COUNT=3;constBACKOFF_MS=100;functionhelper1(){/* ... */}functionhelper2(){/* ... */}// 200 lines later...exportfunctionprocessOrder(){/* ... */}

Documentation Disclosure

Match documentation depth to the reader's likely intent. Most readers want "what does this do?" — very few want "why did you choose bcrypt over argon2?"

Level 1 — CLAUDE.md (5 seconds)
"This is an order processing API. Entry: src/api/server.ts"
Level 2 — Module README (30 seconds) "Orders go through validate → enrich → price → submit. Uses Result types. Retries on transient failures."
Level 3 — Section comments (2 minutes)
// ========================================
// PRICING ENGINE
// ========================================
// Applies tiered discounts, tax rules, and currency conversion.
// See: docs/pricing-model.md for business rules.
Level 4 — Inline "why" comments (as needed)
// Using ceiling division here because partial units // must be billed as full units per the SLA.

API & Type Disclosure

Public interfaces should be scannable summaries. Implementation types stay internal.

// ✅ Public types: minimal, focused, scannable// orders/types.ts — what consumers need to knowexporttypeOrderSummary={id: OrderId;status: OrderStatus;total: Money;itemCount: number;createdAt: DateTime;};// orders/internal-types.ts — implementation detail// Not exported. Contains pricing breakdowns, audit trails,// intermediate computation states, retry metadata, etc.typeOrderPricingContext={/* ... */};typeOrderAuditEntry={/* ... */};

Disclosure Anti-Patterns

  • Premature depth: Putting implementation details in README files
  • Flat disclosure: 500-line files with no visual hierarchy or grouping
  • Inverted disclosure: Helpers at top, public API buried at bottom
  • Missing levels: Jumping from directory listing straight to inline comments with nothing in between

Naming

The #1 impact on readability. Good names eliminate mental translation overhead.

// ✅ Descriptive, unambiguous
async function validateJsonAgainstSchema(
schema: ZodSchema,
input: string
): Promise<ValidationResult>
function calculateExponentialBackoff(
attemptNumber: number,
baseDelayMs: number
): number
// ❌ Vague, abbreviated
async function valJson(s: any, i: string): Promise<any>
function calcBackoff(n: number, d: number): number

Rules:

  1. Be specific: activeUsers not users, httpTimeoutMs not timeout
  2. Include units: delayMs not delay, maxRetries not max
  3. Avoid abbreviations: customer not cust, configuration not cfg
  4. Use domain language: Names from business domain, not technical abstractions
  5. Boolean prefixes: isValid, hasPermission, canEdit, shouldRetry
  6. Verbs for functions: validateEmailFormat() not checkEmail(), fetchActiveUsers() not getUsers()

Function Design

Single Responsibility with Explicit Contracts

// ✅ Self-contained, explicit dependencies, typed contract
async function authenticateUser(
credentials: UserCredentials,
database: Database,
currentTime: DateTime
): Promise<Result<AuthSession, AuthError>> {
// All dependencies visible in signature
// Return type reveals all possible outcomes
}
// ❌ Hidden dependencies, unclear contract
async function auth(data: any): Promise<any> {
// Uses global config, modifies global state
}

Guard Clauses Over Nesting

Handle edge cases first, keep the happy path unindented and visible.

// ✅ Guard clauses — happy path clear
function processOrder(order: Order): Result<Receipt, ProcessError> {
if (!order) return err('missing_order');
if (order.items.length === 0) return err('empty_order');
if (order.total <= 0) return err('invalid_total');
if (!order.paymentMethod) return err('missing_payment');
return ok(completePayment(order));
}
// ❌ Nested conditions — happy path buried
function processOrder(order: Order) {
if (order) {
if (order.items.length > 0) {
if (order.total > 0) {
// Happy path buried 4 levels deep
}
}
}
}

Design Rules

  1. Single responsibility — describable in one sentence
  2. Explicit dependencies — all inputs as parameters, no hidden global state
  3. Type everything — TypeScript strict mode, Python type hints
  4. Self-contained context units — comprehensible without reading other files
  5. 50-line guideline — not a hard limit, but a refactoring trigger

Error Handling

Result Types — Make Errors Explicit

Errors belong in function signatures, not hidden behind throw.

type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
type UserError = 'not_found' | 'unauthorized' | 'network_failure';
async function fetchUser(id: string): Promise<Result<User, UserError>> {
// Errors are part of the contract
}
// Usage forces error handling — compiler catches missing cases
const result = await fetchUser(userId);
if (!result.ok) {
switch (result.error) {
case 'not_found': return show404();
case 'unauthorized': return redirectLogin();
case 'network_failure': return showRetry();
}
}

When to use Result types: API calls, file I/O, validation, any complex error path. When to use exceptions: Truly exceptional/unrecoverable situations (out of memory, corrupted state).

Branded Types — Validate at Boundaries

type ValidatedEmail = string & { readonly __brand: 'ValidatedEmail' };
type UserId = string & { readonly __brand: 'UserId' };
function validateEmail(input: string): ValidatedEmail | null {
return isValidEmail(input) ? (input as ValidatedEmail) : null;
}
// Type system prevents using unvalidated data
function sendEmail(to: ValidatedEmail, subject: string) {
// No need to re-validate — type guarantees validity
}

Once you have a ValidatedEmail, downstream functions carry zero validation overhead. The type system encodes the knowledge that validation occurred.

Error Principles

  1. Never silently swallow errors — log or propagate, never ignore
  2. Fail fast at boundaries — validate inputs immediately, not deep in call stack
  3. Provide actionable messages — what failed, expected vs actual, how to fix
// ✅ Actionable error with context
throw new ValidationError(
`Email validation failed for "user_email": ` +
`Expected "name@domain.com", received "${input}". ` +
`Use validateEmailFormat() to check before calling.`
);
// ❌ Opaque
throw new Error("Validation failed");

File & Module Organization

Structure with Clear Boundaries

// ========================================
// PUBLIC API
// ========================================
export class UserService {
constructor(private readonly db: Database) {}
async createUser(data: CreateUserData): Promise<Result<User, CreateError>> {
// Public interface
}
}
// ========================================
// VALIDATION
// ========================================
function validateUserData(data: unknown): Result<ValidatedData, ValidationError> {
// Grouped validation logic
}
// ========================================
// PRIVATE HELPERS
// ========================================
function hashPassword(password: string): Promise<HashedPassword> {
// Internal implementation
}

Organization Rules

  1. Group by feature/domain, not file type — authentication/, orders/, payments/
  2. Public API first — exported functions at top, helpers at bottom
  3. One major export per fileUserService.ts exports UserService
  4. Co-locate testsUserService.test.ts next to UserService.ts
  5. 300-line guideline — not a hard limit, but a refactoring trigger
  6. Minimal cross-module dependencies — each module is a clean context boundary
project/
├── authentication/ # Self-contained context
│ ├── index.ts # Public API only
│ ├── credentials.ts
│ ├── sessions.ts
│ └── README.md # Module architecture
├── orders/ # Independent context
└── storage/ # Independent context

Testing

Testing Trophy — Mostly Integration

"Write tests. Not too many. Mostly integration." — Kent C. Dodds

  1. Static Analysis (foundation): TypeScript strict mode, ESLint
  2. Unit Tests (narrow): Pure functions, complex algorithms
  3. Integration Tests (widest — most tests here): How pieces work together, where bugs actually live
  4. E2E Tests (top): Critical user journeys only

Tests as Documentation

Test names describe scenarios. Docstrings explain "why." Tests demonstrate usage.

test('should reject invalid credentials without revealing if username exists', async () => {
// Prevents username enumeration attacks
const auth = new Authenticator(database);
const result = await auth.authenticate({
email: 'nonexistent@example.com',
password: 'any-password'
});
expect(result.ok).toBe(false);
expect(result.error.code).toBe('INVALID_CREDENTIALS');
expect(result.error.message).not.toContain('user not found');
});

Testing Rules

  1. Test behavior, not implementation — focus on inputs/outputs, not internal state
  2. One concept per test — don't test multiple unrelated things
  3. Integration over unit — test pieces working together (more confidence per test, more resilient to refactoring)
  4. Clear test names — describe the scenario: test('user can add items to cart')
  5. 80% coverage minimum — focus on critical paths

Observability

Structured Logging

// ✅ Structured — queryable, correlated
logger.info('Request processed', {
request_id: requestId,
user_id: userId,
endpoint: req.path,
method: req.method,
duration_ms: duration,
status_code: res.statusCode,
cache_hit: cacheHit
});
// ❌ Unstructured — hard to query
logger.info(`User ${userId} accessed ${req.path}`);

What to Log

Always include: request_id, user_id, trace_id, entity IDs, operation type, duration_ms, error details.

Log at critical boundaries:

  • External API calls (request/response)
  • Database operations (query, duration)
  • Authentication/authorization decisions
  • Error occurrences with full context

One structured event per operation — derive metrics, logs, or traces from the same data. Don't instrument separately for each observability pillar.

Agentic Coding Patterns

These patterns address the unique demands of code that will be read, modified, and executed by AI agents alongside humans.

Idempotent Operations

Agents retry. Network calls fail. Tasks get re-run. Design every mutation to be safely repeatable.

// ✅ Idempotent — safe to retry
async function ensureUserExists(
email: ValidatedEmail,
db: Database
): Promise<User> {
const existing = await db.users.findByEmail(email);
if (existing) return existing;
return db.users.create({ email });
}
// ❌ Non-idempotent — duplicates on retry
async function createUser(email: string, db: Database): Promise<User> {
return db.users.create({ email });
}

Explicit State Machines Over Implicit Flows

When operations have distinct phases, model them explicitly. Agents reason about state machines far better than implicit status flags scattered across objects.

type OrderState =
| { status: 'draft'; items: Item[] }
| { status: 'submitted'; items: Item[]; submittedAt: DateTime }
| { status: 'paid'; items: Item[]; submittedAt: DateTime; paymentId: string }
| { status: 'shipped'; items: Item[]; trackingNumber: string };
// Each transition is a pure function with clear preconditions
function submitOrder(order: OrderState & { status: 'draft' }): OrderState & { status: 'submitted' } {
return { ...order, status: 'submitted', submittedAt: DateTime.now() };
}

Machine-Parseable Errors

Agents need structured errors alongside human-readable ones. Return error codes that can be programmatically matched, with messages that explain context.

type AppError = {
code: 'VALIDATION_FAILED' | 'NOT_FOUND' | 'CONFLICT' | 'UPSTREAM_TIMEOUT';
message: string; // Human-readable explanation
field?: string; // Which input caused it
retryable: boolean; // Can the caller retry?
};

Atomic, Independently-Verifiable Changes

Structure work so each change can be validated in isolation. This applies to commits, PRs, and function design. An agent (or reviewer) should be able to verify correctness without understanding the entire system.

// ✅ Each function is independently testable and verifiable
function parseConfig(raw: string): Result<Config, ParseError> { /* ... */ }
function validateConfig(config: Config): Result<ValidConfig, ValidationError[]> { /* ... */ }
function applyConfig(config: ValidConfig, system: System): Result<void, ApplyError> { /* ... */ }
// ❌ Monolithic — must understand everything to verify anything
function loadAndApplyConfig(path: string): void { /* 200 lines */ }

Convention Over Configuration

Reduce the search space for agents (and humans). Consistent patterns mean less context needed per decision.

  • Consistent file naming: UserService.ts, UserService.test.ts, UserService.types.ts
  • Predictable directory structure across features
  • Standard patterns for CRUD operations, API endpoints, error handling
  • If your project has a pattern, follow it. If it doesn't, establish one and document it

Contract-First Design

Define types before implementation. Types are the cheapest, most scannable form of documentation. An agent reading your types understands your system's data flow without reading a single function body.

// Define the contract first
interface OrderService {
create(data: CreateOrderInput): Promise<Result<Order, CreateOrderError>>;
cancel(id: OrderId, reason: CancelReason): Promise<Result<void, CancelError>>;
findByUser(userId: UserId, pagination: Pagination): Promise<PaginatedResult<OrderSummary>>;
}
// Then implement — the types guide everything

Observable Side Effects

Every mutation should produce structured output describing what changed. This enables agents to verify their actions and enables humans to audit.

type MutationResult<T> = {
data: T;
changes: Change[]; // What was modified
warnings: string[]; // Non-fatal issues encountered
};
async function updateUserProfile(
id: UserId,
updates: ProfileUpdates
): Promise<Result<MutationResult<UserProfile>, UpdateError>> {
// Returns both the result AND a description of what changed
}

Context Optimisation & Token Economics

Every token an agent reads is a token it can't use for reasoning. Treat context like memory in an embedded system — budget it, measure it, and refuse to waste it.

The Context Budget

AI agents operate within fixed context windows. Your code, documentation, error messages, and tool outputs all compete for the same finite space. Code that is token-efficient isn't just neat — it directly improves agent reasoning quality.

Context Window (finite)
├── System prompt & instructions ~2-5k tokens (fixed cost)
├── Conversation history ~variable
├── Tool definitions ~1-10k tokens (per tool schema)
├── Retrieved code / docs ~variable ← YOU CONTROL THIS
├── Agent reasoning ~variable ← THIS GETS SQUEEZED
└── Output generation ~variable ← AND SO DOES THIS
The more tokens your code consumes, the less room the agent has to think. Optimise ruthlessly.

Semantic Compression

Collapse granular interfaces into high-level semantic operations. Instead of exposing every low-level action, expose intent-based APIs.

// ❌ 15 granular tools = ~15k tokens of schema// An agent must read and reason about ALL of them
tools: [createFile,readFile,deleteFile,moveFile,copyFile,listDirectory,createDirectory,deleteDirectory,getFileMetadata,setFilePermissions,watchFile,compressFile,decompressFile,hashFile,diffFiles]// ✅ 1 semantic dispatcher = ~1k tokens of schema// Agent reasons about intent, not mechanics
tools: [{name: "filesystem",description: "Manage files and directories",parameters: {operation: "create | read | delete | move | copy | list | ...",path: "string",options: "object (operation-specific)"}}]

This is the dispatcher pattern: consolidate related tools behind a single entry point that routes by intent. Token cost drops dramatically while functionality stays the same.

Layered Context Loading

Don't front-load everything. Provide summaries first, with drill-down paths for when the agent actually needs more detail.

// ✅ Layered: summary first, details on demandfunctiongetProjectOverview(): ProjectSummary{return{name: "DataPipeline",modules: ["ingestion","transform","export"],entryPoint: "src/main.ts",recentChanges: getRecentChangeSummary(5),// Drill-down references — agent only loads what it needsgetModuleDetail: (name: string)=>loadModuleContext(name),getFileContent: (path: string)=>loadFileContext(path),};}// ❌ Eager: dumps everything into context upfrontfunctiongetProjectContext(): FullProjectDump{return{allFiles: readAllFiles(),// 50k tokensallTests: readAllTests(),// 30k tokensallDocs: readAllDocs(),// 20k tokens// Agent's context window is now full before it starts thinking};}

Token-Aware Documentation

Write documentation that serves both human readers and token budgets. Every word should earn its place.

# ❌ Token-heavy: narrative style, repetitive, verbose## Overview of the Authentication Module
The authentication module is responsible for handling all aspects of user authentication within our application. This module was designed with security best practices in mind and implements industry-standard protocols. The module handles user login, token generation, session management, and token refresh functionality. It is important to note that this module uses JWT tokens for authentication purposes.
(~80 tokens to say what could be said in 15)
# ✅ Token-efficient: dense, scannable, no filler## Authentication
JWT-based auth with refresh token rotation.
- Entry: `authenticate()``Result<Session, AuthError>`- Tokens: 15min access, 7d refresh (HTTP-only cookie)
- Storage: PostgreSQL users, Redis token blacklist
(~40 tokens, more information conveyed)

Structured Output for Agent Consumption

When building tools or functions that agents will consume, prefer structured, parseable output over human-readable prose.

// ✅ Agent-friendly: structured, parseable, minimaltypeBuildResult={success: boolean;errors: {file: string;line: number;code: string;message: string}[];warnings: {file: string;line: number;code: string;message: string}[];stats: {duration_ms: number;filesProcessed: number};};// ❌ Human-only: requires parsing natural languagefunctiongetBuildOutput(): string{return`Build completed with 2 errors and 1 warning. Error in src/auth.ts line 42: Type 'string' is not assignable... Error in src/orders.ts line 18: Property 'id' does not exist... Warning in src/utils.ts line 7: Unused variable 'temp'... Build took 3.2 seconds, processed 47 files.`;}

Context Boundaries as Architecture

Design modules so an agent can work within one module without loading others. Each module should be a self-contained context unit.

// ✅ Clean context boundary — agent only needs this module// payments/index.tsexportinterfacePaymentService{charge(input: ChargeInput): Promise<Result<Payment,PaymentError>>;refund(id: PaymentId,reason: RefundReason): Promise<Result<Refund,RefundError>>;}// payments/types.ts — all types co-located, no external dependenciesexporttypeChargeInput={amount: Money;method: PaymentMethod;idempotencyKey: string;// Agent-friendly: built-in retry safety};// payments/errors.ts — exhaustive, machine-readableexporttypePaymentError=|{code: 'INSUFFICIENT_FUNDS';available: Money}|{code: 'CARD_DECLINED';reason: string;retryable: false}|{code: 'GATEWAY_TIMEOUT';retryable: true};
// ❌ Leaky context boundary — agent must load 4 modules to understand 1
// payments/index.ts
import { User } from '../users/types';
import { Order } from '../orders/types';
import { AuditLogger } from '../audit/logger';
import { ConfigManager } from '../config/manager';
// Agent now needs context from users/, orders/, audit/, config/

Compression Strategies Reference

StrategyBeforeAfterSavings
Semantic dispatchersN tool schemas (~N × 1k tokens)1 dispatcher (~1k tokens)~(N-1)k tokens
Layered loadingFull dump (50k tokens)Summary + drill-down (2k + on-demand)~48k idle tokens
Dense docsNarrative prose (~80 tokens/concept)Structured bullets (~40 tokens/concept)~50%
Co-located typesScattered across modulesSingle types.ts per moduleFewer file loads
Summary-first returnsFull object graphsSummary + reference IDs60-90% per call
Discriminated unionsGeneric error + message stringTyped union with code fieldEliminates parsing

Project Navigation

CLAUDE.md at Project Root

Every project needs a navigation file. List entry points, patterns, and common tasks.

# Project: Data Processing Pipeline
## Entry Points
- `src/main.ts`: CLI interface
- `src/api/server.ts`: REST API
- `src/processors/pipeline.ts`: Core processing
## Key Patterns
- All processors implement `Processor` interface (src/processors/base.ts)
- Config uses Zod schemas (src/config/schemas.ts)
- External APIs via `APIClient` (src/external/client.ts)
## Common Tasks
- Add data source → implement `DataSource` in `src/api/sources/`
- Add transformation → implement `Transformer` in `src/processors/transformers/`

Keep under 200 lines. Update when architecture changes.

Module-Level READMEs

Every major directory gets a README answering: What is this? How does it work? What are the gotchas?

# Module: User Authentication
## Purpose
JWT-based authentication with refresh token rotation
## Key Decisions
- bcrypt cost factor 12 for password hashing
- Access tokens expire after 15 minutes
- Refresh tokens stored in HTTP-only cookies
## Dependencies
- jose library for JWT (not jsonwebtoken — more secure)
- PostgreSQL for user storage
- Redis for token blacklist

Progressive Context Hierarchy

  1. CLAUDE.md / README.md at root — system overview, entry points, setup
  2. README.md per major module — module purpose, key decisions, patterns
  3. Section comments in files — group related code with clear headers
  4. Function/class docs — purpose, examples for non-obvious APIs
  5. Inline comments — only for "why" decisions

Anti-Patterns

  • Premature optimization — Measure first, optimize second
  • Hasty abstractions — Wait for 3rd duplication before extracting
  • Clever code — Simple and obvious beats clever and compact
  • Silent failures — Log and propagate, never swallow
  • Vague interfacesprocess(data: any): any provides zero guidance
  • Hidden dependencies — Global state, singletons, ambient imports
  • Nested conditionals — Use guard clauses instead
  • Comments describing "what" — If you need a comment to explain what code does, rename things
  • Premature generalization — Build for today's requirements, not hypothetical futures
  • Token bloat — Functions returning everything when callers need summaries
  • Inverted disclosure — Helpers at top, public API buried at bottom
  • Flat files — 500-line files with no visual hierarchy, grouping, or section comments
  • Leaky context boundaries — Modules that import heavily from siblings, forcing agents to load the entire codebase
  • Eager context loading — Dumping full project state into agent context when a summary would suffice

Checklist

Before submitting code:

  • Solves the stated problem with minimal code?
  • A new developer can understand it without extensive context?
  • Errors handled with actionable messages?
  • Names clear, specific, and unambiguous?
  • Functions have single, clear responsibilities?
  • Dependencies explicit (no hidden global state)?
  • Tests cover critical paths?
  • Operations idempotent where applicable?
  • Types define contracts before implementation?
  • Would this work well with ~200 lines of surrounding context?
  • Can an agent understand this module without loading adjacent modules?
  • Are public APIs scannable in under 50 lines?
  • Do tool/function outputs use structured types, not prose?
  • Is documentation token-dense (no filler words, no repetition)?
  • Does the file follow progressive disclosure (types → public API → helpers)?

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

About

A quick reference to the living codestyle guidelines I feed to my AI agents, to help maintain scalable repositories.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

(See CLAUDE.md for sharper context-saving alternative)

Code Style Guide

Core Principle: Context is finite. Every token — code, comment, structure — competes for limited attention. Maximize signal, minimize noise. Write for two audiences: humans with limited working memory and AI agents with bounded context windows.

Philosophy

The optimal code is the minimum necessary to solve the problem correctly. Every additional line is debt.

Progressive Disclosure: Structure code layer-by-layer. Readers grasp high-level flow immediately, drilling into details only when needed. File names indicate purpose. Directory structures mirror conceptual hierarchies. Function names describe behavior without reading implementation. See Progressive Disclosure for concrete patterns.

Self-Documenting: Names eliminate need for comments. Comments explain "why," never "what." If you chose algorithm A over B for subtle reasons, state that. If you're working around a library bug, explain it.

Aggressive Minimalism: Before adding code, ask: "Is this the simplest solution?" Before adding a comment: "Does this clarify something non-obvious?" Before introducing an abstraction: "Does this reduce complexity, or merely relocate it?"

AHA Over DRY: Avoid Hasty Abstractions. Wait for the 3rd duplication before extracting. The wrong abstraction is worse than duplication. Three similar lines of code is better than a premature abstraction.

Progressive Disclosure

Structure every layer of your system so readers — human or agent — get the right level of detail at the right time. No one should need to read 2000 lines to understand what a module does.

The Zoom Principle

Code should work like a map: zoom out for the big picture, zoom in for street-level detail. Each zoom level should be self-sufficient.

// Level 0: Directory structure tells you what exists
src/
├── authentication/ # "There's an auth system"
├── orders/ # "There's an order system" ├── payments/ # "There's a payment system"
└── README.md # How they connect
// Level 1: Index file tells you what it can do
// authentication/index.ts
export { authenticateUser } from './authenticate';
export { refreshSession } from './sessions';
export { revokeAccess } from './revoke';
// No implementation visible — just capabilities
// Level 2: Function signature tells you the contract
async function authenticateUser(
credentials: UserCredentials,
db: Database,
clock: Clock
): Promise<Result<AuthSession, AuthError>>
// Level 3: Implementation tells you how
// Only read this when you need to change the behaviour

File-Level Disclosure

Every file should answer "what is this?" in its first 10 lines. Implementation details belong below.

// ✅ Top of file reveals purpose, contract, and shape/** * Order Processing Pipeline *  * Validates → enriches → prices → submits orders. * Entry point: processOrder() * Error strategy: Result types, no throws */// Types first — the contracttypeProcessOrderInput={/* ... */};typeProcessOrderResult=Result<Receipt,ProcessError>;// Public API secondexportasyncfunctionprocessOrder(input: ProcessOrderInput): Promise<ProcessOrderResult>{constvalidated=validateOrder(input);if(!validated.ok)returnvalidated;constenriched=awaitenrichWithInventory(validated.value);if(!enriched.ok)returnenriched;returnsubmitOrder(enriched.value);}// Private helpers last — only read if you need to understand a specific stepfunctionvalidateOrder(input: ProcessOrderInput): Result<ValidatedOrder,ProcessError>{// ...}
// ❌ Implementation soup — must read everything to understand anythingimport{db}from'../globals';constRETRY_COUNT=3;constBACKOFF_MS=100;functionhelper1(){/* ... */}functionhelper2(){/* ... */}// 200 lines later...exportfunctionprocessOrder(){/* ... */}

Documentation Disclosure

Match documentation depth to the reader's likely intent. Most readers want "what does this do?" — very few want "why did you choose bcrypt over argon2?"

Level 1 — CLAUDE.md (5 seconds)
"This is an order processing API. Entry: src/api/server.ts"
Level 2 — Module README (30 seconds) "Orders go through validate → enrich → price → submit. Uses Result types. Retries on transient failures."
Level 3 — Section comments (2 minutes)
// ========================================
// PRICING ENGINE
// ========================================
// Applies tiered discounts, tax rules, and currency conversion.
// See: docs/pricing-model.md for business rules.
Level 4 — Inline "why" comments (as needed)
// Using ceiling division here because partial units // must be billed as full units per the SLA.

API & Type Disclosure

Public interfaces should be scannable summaries. Implementation types stay internal.

// ✅ Public types: minimal, focused, scannable// orders/types.ts — what consumers need to knowexporttypeOrderSummary={id: OrderId;status: OrderStatus;total: Money;itemCount: number;createdAt: DateTime;};// orders/internal-types.ts — implementation detail// Not exported. Contains pricing breakdowns, audit trails,// intermediate computation states, retry metadata, etc.typeOrderPricingContext={/* ... */};typeOrderAuditEntry={/* ... */};

Disclosure Anti-Patterns

  • Premature depth: Putting implementation details in README files
  • Flat disclosure: 500-line files with no visual hierarchy or grouping
  • Inverted disclosure: Helpers at top, public API buried at bottom
  • Missing levels: Jumping from directory listing straight to inline comments with nothing in between

Naming

The #1 impact on readability. Good names eliminate mental translation overhead.

// ✅ Descriptive, unambiguous
async function validateJsonAgainstSchema(
schema: ZodSchema,
input: string
): Promise<ValidationResult>
function calculateExponentialBackoff(
attemptNumber: number,
baseDelayMs: number
): number
// ❌ Vague, abbreviated
async function valJson(s: any, i: string): Promise<any>
function calcBackoff(n: number, d: number): number

Rules:

  1. Be specific: activeUsers not users, httpTimeoutMs not timeout
  2. Include units: delayMs not delay, maxRetries not max
  3. Avoid abbreviations: customer not cust, configuration not cfg
  4. Use domain language: Names from business domain, not technical abstractions
  5. Boolean prefixes: isValid, hasPermission, canEdit, shouldRetry
  6. Verbs for functions: validateEmailFormat() not checkEmail(), fetchActiveUsers() not getUsers()

Function Design

Single Responsibility with Explicit Contracts

// ✅ Self-contained, explicit dependencies, typed contract
async function authenticateUser(
credentials: UserCredentials,
database: Database,
currentTime: DateTime
): Promise<Result<AuthSession, AuthError>> {
// All dependencies visible in signature
// Return type reveals all possible outcomes
}
// ❌ Hidden dependencies, unclear contract
async function auth(data: any): Promise<any> {
// Uses global config, modifies global state
}

Guard Clauses Over Nesting

Handle edge cases first, keep the happy path unindented and visible.

// ✅ Guard clauses — happy path clear
function processOrder(order: Order): Result<Receipt, ProcessError> {
if (!order) return err('missing_order');
if (order.items.length === 0) return err('empty_order');
if (order.total <= 0) return err('invalid_total');
if (!order.paymentMethod) return err('missing_payment');
return ok(completePayment(order));
}
// ❌ Nested conditions — happy path buried
function processOrder(order: Order) {
if (order) {
if (order.items.length > 0) {
if (order.total > 0) {
// Happy path buried 4 levels deep
}
}
}
}

Design Rules

  1. Single responsibility — describable in one sentence
  2. Explicit dependencies — all inputs as parameters, no hidden global state
  3. Type everything — TypeScript strict mode, Python type hints
  4. Self-contained context units — comprehensible without reading other files
  5. 50-line guideline — not a hard limit, but a refactoring trigger

Error Handling

Result Types — Make Errors Explicit

Errors belong in function signatures, not hidden behind throw.

type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
type UserError = 'not_found' | 'unauthorized' | 'network_failure';
async function fetchUser(id: string): Promise<Result<User, UserError>> {
// Errors are part of the contract
}
// Usage forces error handling — compiler catches missing cases
const result = await fetchUser(userId);
if (!result.ok) {
switch (result.error) {
case 'not_found': return show404();
case 'unauthorized': return redirectLogin();
case 'network_failure': return showRetry();
}
}

When to use Result types: API calls, file I/O, validation, any complex error path. When to use exceptions: Truly exceptional/unrecoverable situations (out of memory, corrupted state).

Branded Types — Validate at Boundaries

type ValidatedEmail = string & { readonly __brand: 'ValidatedEmail' };
type UserId = string & { readonly __brand: 'UserId' };
function validateEmail(input: string): ValidatedEmail | null {
return isValidEmail(input) ? (input as ValidatedEmail) : null;
}
// Type system prevents using unvalidated data
function sendEmail(to: ValidatedEmail, subject: string) {
// No need to re-validate — type guarantees validity
}

Once you have a ValidatedEmail, downstream functions carry zero validation overhead. The type system encodes the knowledge that validation occurred.

Error Principles

  1. Never silently swallow errors — log or propagate, never ignore
  2. Fail fast at boundaries — validate inputs immediately, not deep in call stack
  3. Provide actionable messages — what failed, expected vs actual, how to fix
// ✅ Actionable error with context
throw new ValidationError(
`Email validation failed for "user_email": ` +
`Expected "name@domain.com", received "${input}". ` +
`Use validateEmailFormat() to check before calling.`
);
// ❌ Opaque
throw new Error("Validation failed");

File & Module Organization

Structure with Clear Boundaries

// ========================================
// PUBLIC API
// ========================================
export class UserService {
constructor(private readonly db: Database) {}
async createUser(data: CreateUserData): Promise<Result<User, CreateError>> {
// Public interface
}
}
// ========================================
// VALIDATION
// ========================================
function validateUserData(data: unknown): Result<ValidatedData, ValidationError> {
// Grouped validation logic
}
// ========================================
// PRIVATE HELPERS
// ========================================
function hashPassword(password: string): Promise<HashedPassword> {
// Internal implementation
}

Organization Rules

  1. Group by feature/domain, not file type — authentication/, orders/, payments/
  2. Public API first — exported functions at top, helpers at bottom
  3. One major export per fileUserService.ts exports UserService
  4. Co-locate testsUserService.test.ts next to UserService.ts
  5. 300-line guideline — not a hard limit, but a refactoring trigger
  6. Minimal cross-module dependencies — each module is a clean context boundary
project/
├── authentication/ # Self-contained context
│ ├── index.ts # Public API only
│ ├── credentials.ts
│ ├── sessions.ts
│ └── README.md # Module architecture
├── orders/ # Independent context
└── storage/ # Independent context

Testing

Testing Trophy — Mostly Integration

"Write tests. Not too many. Mostly integration." — Kent C. Dodds

  1. Static Analysis (foundation): TypeScript strict mode, ESLint
  2. Unit Tests (narrow): Pure functions, complex algorithms
  3. Integration Tests (widest — most tests here): How pieces work together, where bugs actually live
  4. E2E Tests (top): Critical user journeys only

Tests as Documentation

Test names describe scenarios. Docstrings explain "why." Tests demonstrate usage.

test('should reject invalid credentials without revealing if username exists', async () => {
// Prevents username enumeration attacks
const auth = new Authenticator(database);
const result = await auth.authenticate({
email: 'nonexistent@example.com',
password: 'any-password'
});
expect(result.ok).toBe(false);
expect(result.error.code).toBe('INVALID_CREDENTIALS');
expect(result.error.message).not.toContain('user not found');
});

Testing Rules

  1. Test behavior, not implementation — focus on inputs/outputs, not internal state
  2. One concept per test — don't test multiple unrelated things
  3. Integration over unit — test pieces working together (more confidence per test, more resilient to refactoring)
  4. Clear test names — describe the scenario: test('user can add items to cart')
  5. 80% coverage minimum — focus on critical paths

Observability

Structured Logging

// ✅ Structured — queryable, correlated
logger.info('Request processed', {
request_id: requestId,
user_id: userId,
endpoint: req.path,
method: req.method,
duration_ms: duration,
status_code: res.statusCode,
cache_hit: cacheHit
});
// ❌ Unstructured — hard to query
logger.info(`User ${userId} accessed ${req.path}`);

What to Log

Always include: request_id, user_id, trace_id, entity IDs, operation type, duration_ms, error details.

Log at critical boundaries:

  • External API calls (request/response)
  • Database operations (query, duration)
  • Authentication/authorization decisions
  • Error occurrences with full context

One structured event per operation — derive metrics, logs, or traces from the same data. Don't instrument separately for each observability pillar.

Agentic Coding Patterns

These patterns address the unique demands of code that will be read, modified, and executed by AI agents alongside humans.

Idempotent Operations

Agents retry. Network calls fail. Tasks get re-run. Design every mutation to be safely repeatable.

// ✅ Idempotent — safe to retry
async function ensureUserExists(
email: ValidatedEmail,
db: Database
): Promise<User> {
const existing = await db.users.findByEmail(email);
if (existing) return existing;
return db.users.create({ email });
}
// ❌ Non-idempotent — duplicates on retry
async function createUser(email: string, db: Database): Promise<User> {
return db.users.create({ email });
}

Explicit State Machines Over Implicit Flows

When operations have distinct phases, model them explicitly. Agents reason about state machines far better than implicit status flags scattered across objects.

type OrderState =
| { status: 'draft'; items: Item[] }
| { status: 'submitted'; items: Item[]; submittedAt: DateTime }
| { status: 'paid'; items: Item[]; submittedAt: DateTime; paymentId: string }
| { status: 'shipped'; items: Item[]; trackingNumber: string };
// Each transition is a pure function with clear preconditions
function submitOrder(order: OrderState & { status: 'draft' }): OrderState & { status: 'submitted' } {
return { ...order, status: 'submitted', submittedAt: DateTime.now() };
}

Machine-Parseable Errors

Agents need structured errors alongside human-readable ones. Return error codes that can be programmatically matched, with messages that explain context.

type AppError = {
code: 'VALIDATION_FAILED' | 'NOT_FOUND' | 'CONFLICT' | 'UPSTREAM_TIMEOUT';
message: string; // Human-readable explanation
field?: string; // Which input caused it
retryable: boolean; // Can the caller retry?
};

Atomic, Independently-Verifiable Changes

Structure work so each change can be validated in isolation. This applies to commits, PRs, and function design. An agent (or reviewer) should be able to verify correctness without understanding the entire system.

// ✅ Each function is independently testable and verifiable
function parseConfig(raw: string): Result<Config, ParseError> { /* ... */ }
function validateConfig(config: Config): Result<ValidConfig, ValidationError[]> { /* ... */ }
function applyConfig(config: ValidConfig, system: System): Result<void, ApplyError> { /* ... */ }
// ❌ Monolithic — must understand everything to verify anything
function loadAndApplyConfig(path: string): void { /* 200 lines */ }

Convention Over Configuration

Reduce the search space for agents (and humans). Consistent patterns mean less context needed per decision.

  • Consistent file naming: UserService.ts, UserService.test.ts, UserService.types.ts
  • Predictable directory structure across features
  • Standard patterns for CRUD operations, API endpoints, error handling
  • If your project has a pattern, follow it. If it doesn't, establish one and document it

Contract-First Design

Define types before implementation. Types are the cheapest, most scannable form of documentation. An agent reading your types understands your system's data flow without reading a single function body.

// Define the contract first
interface OrderService {
create(data: CreateOrderInput): Promise<Result<Order, CreateOrderError>>;
cancel(id: OrderId, reason: CancelReason): Promise<Result<void, CancelError>>;
findByUser(userId: UserId, pagination: Pagination): Promise<PaginatedResult<OrderSummary>>;
}
// Then implement — the types guide everything

Observable Side Effects

Every mutation should produce structured output describing what changed. This enables agents to verify their actions and enables humans to audit.

type MutationResult<T> = {
data: T;
changes: Change[]; // What was modified
warnings: string[]; // Non-fatal issues encountered
};
async function updateUserProfile(
id: UserId,
updates: ProfileUpdates
): Promise<Result<MutationResult<UserProfile>, UpdateError>> {
// Returns both the result AND a description of what changed
}

Context Optimisation & Token Economics

Every token an agent reads is a token it can't use for reasoning. Treat context like memory in an embedded system — budget it, measure it, and refuse to waste it.

The Context Budget

AI agents operate within fixed context windows. Your code, documentation, error messages, and tool outputs all compete for the same finite space. Code that is token-efficient isn't just neat — it directly improves agent reasoning quality.

Context Window (finite)
├── System prompt & instructions ~2-5k tokens (fixed cost)
├── Conversation history ~variable
├── Tool definitions ~1-10k tokens (per tool schema)
├── Retrieved code / docs ~variable ← YOU CONTROL THIS
├── Agent reasoning ~variable ← THIS GETS SQUEEZED
└── Output generation ~variable ← AND SO DOES THIS
The more tokens your code consumes, the less room the agent has to think. Optimise ruthlessly.

Semantic Compression

Collapse granular interfaces into high-level semantic operations. Instead of exposing every low-level action, expose intent-based APIs.

// ❌ 15 granular tools = ~15k tokens of schema// An agent must read and reason about ALL of them
tools: [createFile,readFile,deleteFile,moveFile,copyFile,listDirectory,createDirectory,deleteDirectory,getFileMetadata,setFilePermissions,watchFile,compressFile,decompressFile,hashFile,diffFiles]// ✅ 1 semantic dispatcher = ~1k tokens of schema// Agent reasons about intent, not mechanics
tools: [{name: "filesystem",description: "Manage files and directories",parameters: {operation: "create | read | delete | move | copy | list | ...",path: "string",options: "object (operation-specific)"}}]

This is the dispatcher pattern: consolidate related tools behind a single entry point that routes by intent. Token cost drops dramatically while functionality stays the same.

Layered Context Loading

Don't front-load everything. Provide summaries first, with drill-down paths for when the agent actually needs more detail.

// ✅ Layered: summary first, details on demandfunctiongetProjectOverview(): ProjectSummary{return{name: "DataPipeline",modules: ["ingestion","transform","export"],entryPoint: "src/main.ts",recentChanges: getRecentChangeSummary(5),// Drill-down references — agent only loads what it needsgetModuleDetail: (name: string)=>loadModuleContext(name),getFileContent: (path: string)=>loadFileContext(path),};}// ❌ Eager: dumps everything into context upfrontfunctiongetProjectContext(): FullProjectDump{return{allFiles: readAllFiles(),// 50k tokensallTests: readAllTests(),// 30k tokensallDocs: readAllDocs(),// 20k tokens// Agent's context window is now full before it starts thinking};}

Token-Aware Documentation

Write documentation that serves both human readers and token budgets. Every word should earn its place.

# ❌ Token-heavy: narrative style, repetitive, verbose## Overview of the Authentication Module
The authentication module is responsible for handling all aspects of user authentication within our application. This module was designed with security best practices in mind and implements industry-standard protocols. The module handles user login, token generation, session management, and token refresh functionality. It is important to note that this module uses JWT tokens for authentication purposes.
(~80 tokens to say what could be said in 15)
# ✅ Token-efficient: dense, scannable, no filler## Authentication
JWT-based auth with refresh token rotation.
- Entry: `authenticate()``Result<Session, AuthError>`- Tokens: 15min access, 7d refresh (HTTP-only cookie)
- Storage: PostgreSQL users, Redis token blacklist
(~40 tokens, more information conveyed)

Structured Output for Agent Consumption

When building tools or functions that agents will consume, prefer structured, parseable output over human-readable prose.

// ✅ Agent-friendly: structured, parseable, minimaltypeBuildResult={success: boolean;errors: {file: string;line: number;code: string;message: string}[];warnings: {file: string;line: number;code: string;message: string}[];stats: {duration_ms: number;filesProcessed: number};};// ❌ Human-only: requires parsing natural languagefunctiongetBuildOutput(): string{return`Build completed with 2 errors and 1 warning. Error in src/auth.ts line 42: Type 'string' is not assignable... Error in src/orders.ts line 18: Property 'id' does not exist... Warning in src/utils.ts line 7: Unused variable 'temp'... Build took 3.2 seconds, processed 47 files.`;}

Context Boundaries as Architecture

Design modules so an agent can work within one module without loading others. Each module should be a self-contained context unit.

// ✅ Clean context boundary — agent only needs this module// payments/index.tsexportinterfacePaymentService{charge(input: ChargeInput): Promise<Result<Payment,PaymentError>>;refund(id: PaymentId,reason: RefundReason): Promise<Result<Refund,RefundError>>;}// payments/types.ts — all types co-located, no external dependenciesexporttypeChargeInput={amount: Money;method: PaymentMethod;idempotencyKey: string;// Agent-friendly: built-in retry safety};// payments/errors.ts — exhaustive, machine-readableexporttypePaymentError=|{code: 'INSUFFICIENT_FUNDS';available: Money}|{code: 'CARD_DECLINED';reason: string;retryable: false}|{code: 'GATEWAY_TIMEOUT';retryable: true};
// ❌ Leaky context boundary — agent must load 4 modules to understand 1
// payments/index.ts
import { User } from '../users/types';
import { Order } from '../orders/types';
import { AuditLogger } from '../audit/logger';
import { ConfigManager } from '../config/manager';
// Agent now needs context from users/, orders/, audit/, config/

Compression Strategies Reference

StrategyBeforeAfterSavings
Semantic dispatchersN tool schemas (~N × 1k tokens)1 dispatcher (~1k tokens)~(N-1)k tokens
Layered loadingFull dump (50k tokens)Summary + drill-down (2k + on-demand)~48k idle tokens
Dense docsNarrative prose (~80 tokens/concept)Structured bullets (~40 tokens/concept)~50%
Co-located typesScattered across modulesSingle types.ts per moduleFewer file loads
Summary-first returnsFull object graphsSummary + reference IDs60-90% per call
Discriminated unionsGeneric error + message stringTyped union with code fieldEliminates parsing

Project Navigation

CLAUDE.md at Project Root

Every project needs a navigation file. List entry points, patterns, and common tasks.

# Project: Data Processing Pipeline
## Entry Points
- `src/main.ts`: CLI interface
- `src/api/server.ts`: REST API
- `src/processors/pipeline.ts`: Core processing
## Key Patterns
- All processors implement `Processor` interface (src/processors/base.ts)
- Config uses Zod schemas (src/config/schemas.ts)
- External APIs via `APIClient` (src/external/client.ts)
## Common Tasks
- Add data source → implement `DataSource` in `src/api/sources/`
- Add transformation → implement `Transformer` in `src/processors/transformers/`

Keep under 200 lines. Update when architecture changes.

Module-Level READMEs

Every major directory gets a README answering: What is this? How does it work? What are the gotchas?

# Module: User Authentication
## Purpose
JWT-based authentication with refresh token rotation
## Key Decisions
- bcrypt cost factor 12 for password hashing
- Access tokens expire after 15 minutes
- Refresh tokens stored in HTTP-only cookies
## Dependencies
- jose library for JWT (not jsonwebtoken — more secure)
- PostgreSQL for user storage
- Redis for token blacklist

Progressive Context Hierarchy

  1. CLAUDE.md / README.md at root — system overview, entry points, setup
  2. README.md per major module — module purpose, key decisions, patterns
  3. Section comments in files — group related code with clear headers
  4. Function/class docs — purpose, examples for non-obvious APIs
  5. Inline comments — only for "why" decisions

Anti-Patterns

  • Premature optimization — Measure first, optimize second
  • Hasty abstractions — Wait for 3rd duplication before extracting
  • Clever code — Simple and obvious beats clever and compact
  • Silent failures — Log and propagate, never swallow
  • Vague interfacesprocess(data: any): any provides zero guidance
  • Hidden dependencies — Global state, singletons, ambient imports
  • Nested conditionals — Use guard clauses instead
  • Comments describing "what" — If you need a comment to explain what code does, rename things
  • Premature generalization — Build for today's requirements, not hypothetical futures
  • Token bloat — Functions returning everything when callers need summaries
  • Inverted disclosure — Helpers at top, public API buried at bottom
  • Flat files — 500-line files with no visual hierarchy, grouping, or section comments
  • Leaky context boundaries — Modules that import heavily from siblings, forcing agents to load the entire codebase
  • Eager context loading — Dumping full project state into agent context when a summary would suffice

Checklist

Before submitting code:

  • Solves the stated problem with minimal code?
  • A new developer can understand it without extensive context?
  • Errors handled with actionable messages?
  • Names clear, specific, and unambiguous?
  • Functions have single, clear responsibilities?
  • Dependencies explicit (no hidden global state)?
  • Tests cover critical paths?
  • Operations idempotent where applicable?
  • Types define contracts before implementation?
  • Would this work well with ~200 lines of surrounding context?
  • Can an agent understand this module without loading adjacent modules?
  • Are public APIs scannable in under 50 lines?
  • Do tool/function outputs use structured types, not prose?
  • Is documentation token-dense (no filler words, no repetition)?
  • Does the file follow progressive disclosure (types → public API → helpers)?

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

About

A quick reference to the living codestyle guidelines I feed to my AI agents, to help maintain scalable repositories.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

(See CLAUDE.md for sharper context-saving alternative)

Code Style Guide

Core Principle: Context is finite. Every token — code, comment, structure — competes for limited attention. Maximize signal, minimize noise. Write for two audiences: humans with limited working memory and AI agents with bounded context windows.

Philosophy

The optimal code is the minimum necessary to solve the problem correctly. Every additional line is debt.

Progressive Disclosure: Structure code layer-by-layer. Readers grasp high-level flow immediately, drilling into details only when needed. File names indicate purpose. Directory structures mirror conceptual hierarchies. Function names describe behavior without reading implementation. See Progressive Disclosure for concrete patterns.

Self-Documenting: Names eliminate need for comments. Comments explain "why," never "what." If you chose algorithm A over B for subtle reasons, state that. If you're working around a library bug, explain it.

Aggressive Minimalism: Before adding code, ask: "Is this the simplest solution?" Before adding a comment: "Does this clarify something non-obvious?" Before introducing an abstraction: "Does this reduce complexity, or merely relocate it?"

AHA Over DRY: Avoid Hasty Abstractions. Wait for the 3rd duplication before extracting. The wrong abstraction is worse than duplication. Three similar lines of code is better than a premature abstraction.

Progressive Disclosure

Structure every layer of your system so readers — human or agent — get the right level of detail at the right time. No one should need to read 2000 lines to understand what a module does.

The Zoom Principle

Code should work like a map: zoom out for the big picture, zoom in for street-level detail. Each zoom level should be self-sufficient.

// Level 0: Directory structure tells you what exists
src/
├── authentication/ # "There's an auth system"
├── orders/ # "There's an order system" ├── payments/ # "There's a payment system"
└── README.md # How they connect
// Level 1: Index file tells you what it can do
// authentication/index.ts
export { authenticateUser } from './authenticate';
export { refreshSession } from './sessions';
export { revokeAccess } from './revoke';
// No implementation visible — just capabilities
// Level 2: Function signature tells you the contract
async function authenticateUser(
credentials: UserCredentials,
db: Database,
clock: Clock
): Promise<Result<AuthSession, AuthError>>
// Level 3: Implementation tells you how
// Only read this when you need to change the behaviour

File-Level Disclosure

Every file should answer "what is this?" in its first 10 lines. Implementation details belong below.

// ✅ Top of file reveals purpose, contract, and shape/** * Order Processing Pipeline *  * Validates → enriches → prices → submits orders. * Entry point: processOrder() * Error strategy: Result types, no throws */// Types first — the contracttypeProcessOrderInput={/* ... */};typeProcessOrderResult=Result<Receipt,ProcessError>;// Public API secondexportasyncfunctionprocessOrder(input: ProcessOrderInput): Promise<ProcessOrderResult>{constvalidated=validateOrder(input);if(!validated.ok)returnvalidated;constenriched=awaitenrichWithInventory(validated.value);if(!enriched.ok)returnenriched;returnsubmitOrder(enriched.value);}// Private helpers last — only read if you need to understand a specific stepfunctionvalidateOrder(input: ProcessOrderInput): Result<ValidatedOrder,ProcessError>{// ...}
// ❌ Implementation soup — must read everything to understand anythingimport{db}from'../globals';constRETRY_COUNT=3;constBACKOFF_MS=100;functionhelper1(){/* ... */}functionhelper2(){/* ... */}// 200 lines later...exportfunctionprocessOrder(){/* ... */}

Documentation Disclosure

Match documentation depth to the reader's likely intent. Most readers want "what does this do?" — very few want "why did you choose bcrypt over argon2?"

Level 1 — CLAUDE.md (5 seconds)
"This is an order processing API. Entry: src/api/server.ts"
Level 2 — Module README (30 seconds) "Orders go through validate → enrich → price → submit. Uses Result types. Retries on transient failures."
Level 3 — Section comments (2 minutes)
// ========================================
// PRICING ENGINE
// ========================================
// Applies tiered discounts, tax rules, and currency conversion.
// See: docs/pricing-model.md for business rules.
Level 4 — Inline "why" comments (as needed)
// Using ceiling division here because partial units // must be billed as full units per the SLA.

API & Type Disclosure

Public interfaces should be scannable summaries. Implementation types stay internal.

// ✅ Public types: minimal, focused, scannable// orders/types.ts — what consumers need to knowexporttypeOrderSummary={id: OrderId;status: OrderStatus;total: Money;itemCount: number;createdAt: DateTime;};// orders/internal-types.ts — implementation detail// Not exported. Contains pricing breakdowns, audit trails,// intermediate computation states, retry metadata, etc.typeOrderPricingContext={/* ... */};typeOrderAuditEntry={/* ... */};

Disclosure Anti-Patterns

  • Premature depth: Putting implementation details in README files
  • Flat disclosure: 500-line files with no visual hierarchy or grouping
  • Inverted disclosure: Helpers at top, public API buried at bottom
  • Missing levels: Jumping from directory listing straight to inline comments with nothing in between

Naming

The #1 impact on readability. Good names eliminate mental translation overhead.

// ✅ Descriptive, unambiguous
async function validateJsonAgainstSchema(
schema: ZodSchema,
input: string
): Promise<ValidationResult>
function calculateExponentialBackoff(
attemptNumber: number,
baseDelayMs: number
): number
// ❌ Vague, abbreviated
async function valJson(s: any, i: string): Promise<any>
function calcBackoff(n: number, d: number): number

Rules:

  1. Be specific: activeUsers not users, httpTimeoutMs not timeout
  2. Include units: delayMs not delay, maxRetries not max
  3. Avoid abbreviations: customer not cust, configuration not cfg
  4. Use domain language: Names from business domain, not technical abstractions
  5. Boolean prefixes: isValid, hasPermission, canEdit, shouldRetry
  6. Verbs for functions: validateEmailFormat() not checkEmail(), fetchActiveUsers() not getUsers()

Function Design

Single Responsibility with Explicit Contracts

// ✅ Self-contained, explicit dependencies, typed contract
async function authenticateUser(
credentials: UserCredentials,
database: Database,
currentTime: DateTime
): Promise<Result<AuthSession, AuthError>> {
// All dependencies visible in signature
// Return type reveals all possible outcomes
}
// ❌ Hidden dependencies, unclear contract
async function auth(data: any): Promise<any> {
// Uses global config, modifies global state
}

Guard Clauses Over Nesting

Handle edge cases first, keep the happy path unindented and visible.

// ✅ Guard clauses — happy path clear
function processOrder(order: Order): Result<Receipt, ProcessError> {
if (!order) return err('missing_order');
if (order.items.length === 0) return err('empty_order');
if (order.total <= 0) return err('invalid_total');
if (!order.paymentMethod) return err('missing_payment');
return ok(completePayment(order));
}
// ❌ Nested conditions — happy path buried
function processOrder(order: Order) {
if (order) {
if (order.items.length > 0) {
if (order.total > 0) {
// Happy path buried 4 levels deep
}
}
}
}

Design Rules

  1. Single responsibility — describable in one sentence
  2. Explicit dependencies — all inputs as parameters, no hidden global state
  3. Type everything — TypeScript strict mode, Python type hints
  4. Self-contained context units — comprehensible without reading other files
  5. 50-line guideline — not a hard limit, but a refactoring trigger

Error Handling

Result Types — Make Errors Explicit

Errors belong in function signatures, not hidden behind throw.

type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
type UserError = 'not_found' | 'unauthorized' | 'network_failure';
async function fetchUser(id: string): Promise<Result<User, UserError>> {
// Errors are part of the contract
}
// Usage forces error handling — compiler catches missing cases
const result = await fetchUser(userId);
if (!result.ok) {
switch (result.error) {
case 'not_found': return show404();
case 'unauthorized': return redirectLogin();
case 'network_failure': return showRetry();
}
}

When to use Result types: API calls, file I/O, validation, any complex error path. When to use exceptions: Truly exceptional/unrecoverable situations (out of memory, corrupted state).

Branded Types — Validate at Boundaries

type ValidatedEmail = string & { readonly __brand: 'ValidatedEmail' };
type UserId = string & { readonly __brand: 'UserId' };
function validateEmail(input: string): ValidatedEmail | null {
return isValidEmail(input) ? (input as ValidatedEmail) : null;
}
// Type system prevents using unvalidated data
function sendEmail(to: ValidatedEmail, subject: string) {
// No need to re-validate — type guarantees validity
}

Once you have a ValidatedEmail, downstream functions carry zero validation overhead. The type system encodes the knowledge that validation occurred.

Error Principles

  1. Never silently swallow errors — log or propagate, never ignore
  2. Fail fast at boundaries — validate inputs immediately, not deep in call stack
  3. Provide actionable messages — what failed, expected vs actual, how to fix
// ✅ Actionable error with context
throw new ValidationError(
`Email validation failed for "user_email": ` +
`Expected "name@domain.com", received "${input}". ` +
`Use validateEmailFormat() to check before calling.`
);
// ❌ Opaque
throw new Error("Validation failed");

File & Module Organization

Structure with Clear Boundaries

// ========================================
// PUBLIC API
// ========================================
export class UserService {
constructor(private readonly db: Database) {}
async createUser(data: CreateUserData): Promise<Result<User, CreateError>> {
// Public interface
}
}
// ========================================
// VALIDATION
// ========================================
function validateUserData(data: unknown): Result<ValidatedData, ValidationError> {
// Grouped validation logic
}
// ========================================
// PRIVATE HELPERS
// ========================================
function hashPassword(password: string): Promise<HashedPassword> {
// Internal implementation
}

Organization Rules

  1. Group by feature/domain, not file type — authentication/, orders/, payments/
  2. Public API first — exported functions at top, helpers at bottom
  3. One major export per fileUserService.ts exports UserService
  4. Co-locate testsUserService.test.ts next to UserService.ts
  5. 300-line guideline — not a hard limit, but a refactoring trigger
  6. Minimal cross-module dependencies — each module is a clean context boundary
project/
├── authentication/ # Self-contained context
│ ├── index.ts # Public API only
│ ├── credentials.ts
│ ├── sessions.ts
│ └── README.md # Module architecture
├── orders/ # Independent context
└── storage/ # Independent context

Testing

Testing Trophy — Mostly Integration

"Write tests. Not too many. Mostly integration." — Kent C. Dodds

  1. Static Analysis (foundation): TypeScript strict mode, ESLint
  2. Unit Tests (narrow): Pure functions, complex algorithms
  3. Integration Tests (widest — most tests here): How pieces work together, where bugs actually live
  4. E2E Tests (top): Critical user journeys only

Tests as Documentation

Test names describe scenarios. Docstrings explain "why." Tests demonstrate usage.

test('should reject invalid credentials without revealing if username exists', async () => {
// Prevents username enumeration attacks
const auth = new Authenticator(database);
const result = await auth.authenticate({
email: 'nonexistent@example.com',
password: 'any-password'
});
expect(result.ok).toBe(false);
expect(result.error.code).toBe('INVALID_CREDENTIALS');
expect(result.error.message).not.toContain('user not found');
});

Testing Rules

  1. Test behavior, not implementation — focus on inputs/outputs, not internal state
  2. One concept per test — don't test multiple unrelated things
  3. Integration over unit — test pieces working together (more confidence per test, more resilient to refactoring)
  4. Clear test names — describe the scenario: test('user can add items to cart')
  5. 80% coverage minimum — focus on critical paths

Observability

Structured Logging

// ✅ Structured — queryable, correlated
logger.info('Request processed', {
request_id: requestId,
user_id: userId,
endpoint: req.path,
method: req.method,
duration_ms: duration,
status_code: res.statusCode,
cache_hit: cacheHit
});
// ❌ Unstructured — hard to query
logger.info(`User ${userId} accessed ${req.path}`);

What to Log

Always include: request_id, user_id, trace_id, entity IDs, operation type, duration_ms, error details.

Log at critical boundaries:

  • External API calls (request/response)
  • Database operations (query, duration)
  • Authentication/authorization decisions
  • Error occurrences with full context

One structured event per operation — derive metrics, logs, or traces from the same data. Don't instrument separately for each observability pillar.

Agentic Coding Patterns

These patterns address the unique demands of code that will be read, modified, and executed by AI agents alongside humans.

Idempotent Operations

Agents retry. Network calls fail. Tasks get re-run. Design every mutation to be safely repeatable.

// ✅ Idempotent — safe to retry
async function ensureUserExists(
email: ValidatedEmail,
db: Database
): Promise<User> {
const existing = await db.users.findByEmail(email);
if (existing) return existing;
return db.users.create({ email });
}
// ❌ Non-idempotent — duplicates on retry
async function createUser(email: string, db: Database): Promise<User> {
return db.users.create({ email });
}

Explicit State Machines Over Implicit Flows

When operations have distinct phases, model them explicitly. Agents reason about state machines far better than implicit status flags scattered across objects.

type OrderState =
| { status: 'draft'; items: Item[] }
| { status: 'submitted'; items: Item[]; submittedAt: DateTime }
| { status: 'paid'; items: Item[]; submittedAt: DateTime; paymentId: string }
| { status: 'shipped'; items: Item[]; trackingNumber: string };
// Each transition is a pure function with clear preconditions
function submitOrder(order: OrderState & { status: 'draft' }): OrderState & { status: 'submitted' } {
return { ...order, status: 'submitted', submittedAt: DateTime.now() };
}

Machine-Parseable Errors

Agents need structured errors alongside human-readable ones. Return error codes that can be programmatically matched, with messages that explain context.

type AppError = {
code: 'VALIDATION_FAILED' | 'NOT_FOUND' | 'CONFLICT' | 'UPSTREAM_TIMEOUT';
message: string; // Human-readable explanation
field?: string; // Which input caused it
retryable: boolean; // Can the caller retry?
};

Atomic, Independently-Verifiable Changes

Structure work so each change can be validated in isolation. This applies to commits, PRs, and function design. An agent (or reviewer) should be able to verify correctness without understanding the entire system.

// ✅ Each function is independently testable and verifiable
function parseConfig(raw: string): Result<Config, ParseError> { /* ... */ }
function validateConfig(config: Config): Result<ValidConfig, ValidationError[]> { /* ... */ }
function applyConfig(config: ValidConfig, system: System): Result<void, ApplyError> { /* ... */ }
// ❌ Monolithic — must understand everything to verify anything
function loadAndApplyConfig(path: string): void { /* 200 lines */ }

Convention Over Configuration

Reduce the search space for agents (and humans). Consistent patterns mean less context needed per decision.

  • Consistent file naming: UserService.ts, UserService.test.ts, UserService.types.ts
  • Predictable directory structure across features
  • Standard patterns for CRUD operations, API endpoints, error handling
  • If your project has a pattern, follow it. If it doesn't, establish one and document it

Contract-First Design

Define types before implementation. Types are the cheapest, most scannable form of documentation. An agent reading your types understands your system's data flow without reading a single function body.

// Define the contract first
interface OrderService {
create(data: CreateOrderInput): Promise<Result<Order, CreateOrderError>>;
cancel(id: OrderId, reason: CancelReason): Promise<Result<void, CancelError>>;
findByUser(userId: UserId, pagination: Pagination): Promise<PaginatedResult<OrderSummary>>;
}
// Then implement — the types guide everything

Observable Side Effects

Every mutation should produce structured output describing what changed. This enables agents to verify their actions and enables humans to audit.

type MutationResult<T> = {
data: T;
changes: Change[]; // What was modified
warnings: string[]; // Non-fatal issues encountered
};
async function updateUserProfile(
id: UserId,
updates: ProfileUpdates
): Promise<Result<MutationResult<UserProfile>, UpdateError>> {
// Returns both the result AND a description of what changed
}

Context Optimisation & Token Economics

Every token an agent reads is a token it can't use for reasoning. Treat context like memory in an embedded system — budget it, measure it, and refuse to waste it.

The Context Budget

AI agents operate within fixed context windows. Your code, documentation, error messages, and tool outputs all compete for the same finite space. Code that is token-efficient isn't just neat — it directly improves agent reasoning quality.

Context Window (finite)
├── System prompt & instructions ~2-5k tokens (fixed cost)
├── Conversation history ~variable
├── Tool definitions ~1-10k tokens (per tool schema)
├── Retrieved code / docs ~variable ← YOU CONTROL THIS
├── Agent reasoning ~variable ← THIS GETS SQUEEZED
└── Output generation ~variable ← AND SO DOES THIS
The more tokens your code consumes, the less room the agent has to think. Optimise ruthlessly.

Semantic Compression

Collapse granular interfaces into high-level semantic operations. Instead of exposing every low-level action, expose intent-based APIs.

// ❌ 15 granular tools = ~15k tokens of schema// An agent must read and reason about ALL of them
tools: [createFile,readFile,deleteFile,moveFile,copyFile,listDirectory,createDirectory,deleteDirectory,getFileMetadata,setFilePermissions,watchFile,compressFile,decompressFile,hashFile,diffFiles]// ✅ 1 semantic dispatcher = ~1k tokens of schema// Agent reasons about intent, not mechanics
tools: [{name: "filesystem",description: "Manage files and directories",parameters: {operation: "create | read | delete | move | copy | list | ...",path: "string",options: "object (operation-specific)"}}]

This is the dispatcher pattern: consolidate related tools behind a single entry point that routes by intent. Token cost drops dramatically while functionality stays the same.

Layered Context Loading

Don't front-load everything. Provide summaries first, with drill-down paths for when the agent actually needs more detail.

// ✅ Layered: summary first, details on demandfunctiongetProjectOverview(): ProjectSummary{return{name: "DataPipeline",modules: ["ingestion","transform","export"],entryPoint: "src/main.ts",recentChanges: getRecentChangeSummary(5),// Drill-down references — agent only loads what it needsgetModuleDetail: (name: string)=>loadModuleContext(name),getFileContent: (path: string)=>loadFileContext(path),};}// ❌ Eager: dumps everything into context upfrontfunctiongetProjectContext(): FullProjectDump{return{allFiles: readAllFiles(),// 50k tokensallTests: readAllTests(),// 30k tokensallDocs: readAllDocs(),// 20k tokens// Agent's context window is now full before it starts thinking};}

Token-Aware Documentation

Write documentation that serves both human readers and token budgets. Every word should earn its place.

# ❌ Token-heavy: narrative style, repetitive, verbose## Overview of the Authentication Module
The authentication module is responsible for handling all aspects of user authentication within our application. This module was designed with security best practices in mind and implements industry-standard protocols. The module handles user login, token generation, session management, and token refresh functionality. It is important to note that this module uses JWT tokens for authentication purposes.
(~80 tokens to say what could be said in 15)
# ✅ Token-efficient: dense, scannable, no filler## Authentication
JWT-based auth with refresh token rotation.
- Entry: `authenticate()``Result<Session, AuthError>`- Tokens: 15min access, 7d refresh (HTTP-only cookie)
- Storage: PostgreSQL users, Redis token blacklist
(~40 tokens, more information conveyed)

Structured Output for Agent Consumption

When building tools or functions that agents will consume, prefer structured, parseable output over human-readable prose.

// ✅ Agent-friendly: structured, parseable, minimaltypeBuildResult={success: boolean;errors: {file: string;line: number;code: string;message: string}[];warnings: {file: string;line: number;code: string;message: string}[];stats: {duration_ms: number;filesProcessed: number};};// ❌ Human-only: requires parsing natural languagefunctiongetBuildOutput(): string{return`Build completed with 2 errors and 1 warning. Error in src/auth.ts line 42: Type 'string' is not assignable... Error in src/orders.ts line 18: Property 'id' does not exist... Warning in src/utils.ts line 7: Unused variable 'temp'... Build took 3.2 seconds, processed 47 files.`;}

Context Boundaries as Architecture

Design modules so an agent can work within one module without loading others. Each module should be a self-contained context unit.

// ✅ Clean context boundary — agent only needs this module// payments/index.tsexportinterfacePaymentService{charge(input: ChargeInput): Promise<Result<Payment,PaymentError>>;refund(id: PaymentId,reason: RefundReason): Promise<Result<Refund,RefundError>>;}// payments/types.ts — all types co-located, no external dependenciesexporttypeChargeInput={amount: Money;method: PaymentMethod;idempotencyKey: string;// Agent-friendly: built-in retry safety};// payments/errors.ts — exhaustive, machine-readableexporttypePaymentError=|{code: 'INSUFFICIENT_FUNDS';available: Money}|{code: 'CARD_DECLINED';reason: string;retryable: false}|{code: 'GATEWAY_TIMEOUT';retryable: true};
// ❌ Leaky context boundary — agent must load 4 modules to understand 1
// payments/index.ts
import { User } from '../users/types';
import { Order } from '../orders/types';
import { AuditLogger } from '../audit/logger';
import { ConfigManager } from '../config/manager';
// Agent now needs context from users/, orders/, audit/, config/

Compression Strategies Reference

StrategyBeforeAfterSavings
Semantic dispatchersN tool schemas (~N × 1k tokens)1 dispatcher (~1k tokens)~(N-1)k tokens
Layered loadingFull dump (50k tokens)Summary + drill-down (2k + on-demand)~48k idle tokens
Dense docsNarrative prose (~80 tokens/concept)Structured bullets (~40 tokens/concept)~50%
Co-located typesScattered across modulesSingle types.ts per moduleFewer file loads
Summary-first returnsFull object graphsSummary + reference IDs60-90% per call
Discriminated unionsGeneric error + message stringTyped union with code fieldEliminates parsing

Project Navigation

CLAUDE.md at Project Root

Every project needs a navigation file. List entry points, patterns, and common tasks.

# Project: Data Processing Pipeline
## Entry Points
- `src/main.ts`: CLI interface
- `src/api/server.ts`: REST API
- `src/processors/pipeline.ts`: Core processing
## Key Patterns
- All processors implement `Processor` interface (src/processors/base.ts)
- Config uses Zod schemas (src/config/schemas.ts)
- External APIs via `APIClient` (src/external/client.ts)
## Common Tasks
- Add data source → implement `DataSource` in `src/api/sources/`
- Add transformation → implement `Transformer` in `src/processors/transformers/`

Keep under 200 lines. Update when architecture changes.

Module-Level READMEs

Every major directory gets a README answering: What is this? How does it work? What are the gotchas?

# Module: User Authentication
## Purpose
JWT-based authentication with refresh token rotation
## Key Decisions
- bcrypt cost factor 12 for password hashing
- Access tokens expire after 15 minutes
- Refresh tokens stored in HTTP-only cookies
## Dependencies
- jose library for JWT (not jsonwebtoken — more secure)
- PostgreSQL for user storage
- Redis for token blacklist

Progressive Context Hierarchy

  1. CLAUDE.md / README.md at root — system overview, entry points, setup
  2. README.md per major module — module purpose, key decisions, patterns
  3. Section comments in files — group related code with clear headers
  4. Function/class docs — purpose, examples for non-obvious APIs
  5. Inline comments — only for "why" decisions

Anti-Patterns

  • Premature optimization — Measure first, optimize second
  • Hasty abstractions — Wait for 3rd duplication before extracting
  • Clever code — Simple and obvious beats clever and compact
  • Silent failures — Log and propagate, never swallow
  • Vague interfacesprocess(data: any): any provides zero guidance
  • Hidden dependencies — Global state, singletons, ambient imports
  • Nested conditionals — Use guard clauses instead
  • Comments describing "what" — If you need a comment to explain what code does, rename things
  • Premature generalization — Build for today's requirements, not hypothetical futures
  • Token bloat — Functions returning everything when callers need summaries
  • Inverted disclosure — Helpers at top, public API buried at bottom
  • Flat files — 500-line files with no visual hierarchy, grouping, or section comments
  • Leaky context boundaries — Modules that import heavily from siblings, forcing agents to load the entire codebase
  • Eager context loading — Dumping full project state into agent context when a summary would suffice

Checklist

Before submitting code:

  • Solves the stated problem with minimal code?
  • A new developer can understand it without extensive context?
  • Errors handled with actionable messages?
  • Names clear, specific, and unambiguous?
  • Functions have single, clear responsibilities?
  • Dependencies explicit (no hidden global state)?
  • Tests cover critical paths?
  • Operations idempotent where applicable?
  • Types define contracts before implementation?
  • Would this work well with ~200 lines of surrounding context?
  • Can an agent understand this module without loading adjacent modules?
  • Are public APIs scannable in under 50 lines?
  • Do tool/function outputs use structured types, not prose?
  • Is documentation token-dense (no filler words, no repetition)?
  • Does the file follow progressive disclosure (types → public API → helpers)?

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

About

A quick reference to the living codestyle guidelines I feed to my AI agents, to help maintain scalable repositories.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

(See CLAUDE.md for sharper context-saving alternative)

Code Style Guide

Core Principle: Context is finite. Every token — code, comment, structure — competes for limited attention. Maximize signal, minimize noise. Write for two audiences: humans with limited working memory and AI agents with bounded context windows.

Philosophy

The optimal code is the minimum necessary to solve the problem correctly. Every additional line is debt.

Progressive Disclosure: Structure code layer-by-layer. Readers grasp high-level flow immediately, drilling into details only when needed. File names indicate purpose. Directory structures mirror conceptual hierarchies. Function names describe behavior without reading implementation. See Progressive Disclosure for concrete patterns.

Self-Documenting: Names eliminate need for comments. Comments explain "why," never "what." If you chose algorithm A over B for subtle reasons, state that. If you're working around a library bug, explain it.

Aggressive Minimalism: Before adding code, ask: "Is this the simplest solution?" Before adding a comment: "Does this clarify something non-obvious?" Before introducing an abstraction: "Does this reduce complexity, or merely relocate it?"

AHA Over DRY: Avoid Hasty Abstractions. Wait for the 3rd duplication before extracting. The wrong abstraction is worse than duplication. Three similar lines of code is better than a premature abstraction.

Progressive Disclosure

Structure every layer of your system so readers — human or agent — get the right level of detail at the right time. No one should need to read 2000 lines to understand what a module does.

The Zoom Principle

Code should work like a map: zoom out for the big picture, zoom in for street-level detail. Each zoom level should be self-sufficient.

// Level 0: Directory structure tells you what exists
src/
├── authentication/ # "There's an auth system"
├── orders/ # "There's an order system" ├── payments/ # "There's a payment system"
└── README.md # How they connect
// Level 1: Index file tells you what it can do
// authentication/index.ts
export { authenticateUser } from './authenticate';
export { refreshSession } from './sessions';
export { revokeAccess } from './revoke';
// No implementation visible — just capabilities
// Level 2: Function signature tells you the contract
async function authenticateUser(
credentials: UserCredentials,
db: Database,
clock: Clock
): Promise<Result<AuthSession, AuthError>>
// Level 3: Implementation tells you how
// Only read this when you need to change the behaviour

File-Level Disclosure

Every file should answer "what is this?" in its first 10 lines. Implementation details belong below.

// ✅ Top of file reveals purpose, contract, and shape/** * Order Processing Pipeline *  * Validates → enriches → prices → submits orders. * Entry point: processOrder() * Error strategy: Result types, no throws */// Types first — the contracttypeProcessOrderInput={/* ... */};typeProcessOrderResult=Result<Receipt,ProcessError>;// Public API secondexportasyncfunctionprocessOrder(input: ProcessOrderInput): Promise<ProcessOrderResult>{constvalidated=validateOrder(input);if(!validated.ok)returnvalidated;constenriched=awaitenrichWithInventory(validated.value);if(!enriched.ok)returnenriched;returnsubmitOrder(enriched.value);}// Private helpers last — only read if you need to understand a specific stepfunctionvalidateOrder(input: ProcessOrderInput): Result<ValidatedOrder,ProcessError>{// ...}
// ❌ Implementation soup — must read everything to understand anythingimport{db}from'../globals';constRETRY_COUNT=3;constBACKOFF_MS=100;functionhelper1(){/* ... */}functionhelper2(){/* ... */}// 200 lines later...exportfunctionprocessOrder(){/* ... */}

Documentation Disclosure

Match documentation depth to the reader's likely intent. Most readers want "what does this do?" — very few want "why did you choose bcrypt over argon2?"

Level 1 — CLAUDE.md (5 seconds)
"This is an order processing API. Entry: src/api/server.ts"
Level 2 — Module README (30 seconds) "Orders go through validate → enrich → price → submit. Uses Result types. Retries on transient failures."
Level 3 — Section comments (2 minutes)
// ========================================
// PRICING ENGINE
// ========================================
// Applies tiered discounts, tax rules, and currency conversion.
// See: docs/pricing-model.md for business rules.
Level 4 — Inline "why" comments (as needed)
// Using ceiling division here because partial units // must be billed as full units per the SLA.

API & Type Disclosure

Public interfaces should be scannable summaries. Implementation types stay internal.

// ✅ Public types: minimal, focused, scannable// orders/types.ts — what consumers need to knowexporttypeOrderSummary={id: OrderId;status: OrderStatus;total: Money;itemCount: number;createdAt: DateTime;};// orders/internal-types.ts — implementation detail// Not exported. Contains pricing breakdowns, audit trails,// intermediate computation states, retry metadata, etc.typeOrderPricingContext={/* ... */};typeOrderAuditEntry={/* ... */};

Disclosure Anti-Patterns

  • Premature depth: Putting implementation details in README files
  • Flat disclosure: 500-line files with no visual hierarchy or grouping
  • Inverted disclosure: Helpers at top, public API buried at bottom
  • Missing levels: Jumping from directory listing straight to inline comments with nothing in between

Naming

The #1 impact on readability. Good names eliminate mental translation overhead.

// ✅ Descriptive, unambiguous
async function validateJsonAgainstSchema(
schema: ZodSchema,
input: string
): Promise<ValidationResult>
function calculateExponentialBackoff(
attemptNumber: number,
baseDelayMs: number
): number
// ❌ Vague, abbreviated
async function valJson(s: any, i: string): Promise<any>
function calcBackoff(n: number, d: number): number

Rules:

  1. Be specific: activeUsers not users, httpTimeoutMs not timeout
  2. Include units: delayMs not delay, maxRetries not max
  3. Avoid abbreviations: customer not cust, configuration not cfg
  4. Use domain language: Names from business domain, not technical abstractions
  5. Boolean prefixes: isValid, hasPermission, canEdit, shouldRetry
  6. Verbs for functions: validateEmailFormat() not checkEmail(), fetchActiveUsers() not getUsers()

Function Design

Single Responsibility with Explicit Contracts

// ✅ Self-contained, explicit dependencies, typed contract
async function authenticateUser(
credentials: UserCredentials,
database: Database,
currentTime: DateTime
): Promise<Result<AuthSession, AuthError>> {
// All dependencies visible in signature
// Return type reveals all possible outcomes
}
// ❌ Hidden dependencies, unclear contract
async function auth(data: any): Promise<any> {
// Uses global config, modifies global state
}

Guard Clauses Over Nesting

Handle edge cases first, keep the happy path unindented and visible.

// ✅ Guard clauses — happy path clear
function processOrder(order: Order): Result<Receipt, ProcessError> {
if (!order) return err('missing_order');
if (order.items.length === 0) return err('empty_order');
if (order.total <= 0) return err('invalid_total');
if (!order.paymentMethod) return err('missing_payment');
return ok(completePayment(order));
}
// ❌ Nested conditions — happy path buried
function processOrder(order: Order) {
if (order) {
if (order.items.length > 0) {
if (order.total > 0) {
// Happy path buried 4 levels deep
}
}
}
}

Design Rules

  1. Single responsibility — describable in one sentence
  2. Explicit dependencies — all inputs as parameters, no hidden global state
  3. Type everything — TypeScript strict mode, Python type hints
  4. Self-contained context units — comprehensible without reading other files
  5. 50-line guideline — not a hard limit, but a refactoring trigger

Error Handling

Result Types — Make Errors Explicit

Errors belong in function signatures, not hidden behind throw.

type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
type UserError = 'not_found' | 'unauthorized' | 'network_failure';
async function fetchUser(id: string): Promise<Result<User, UserError>> {
// Errors are part of the contract
}
// Usage forces error handling — compiler catches missing cases
const result = await fetchUser(userId);
if (!result.ok) {
switch (result.error) {
case 'not_found': return show404();
case 'unauthorized': return redirectLogin();
case 'network_failure': return showRetry();
}
}

When to use Result types: API calls, file I/O, validation, any complex error path. When to use exceptions: Truly exceptional/unrecoverable situations (out of memory, corrupted state).

Branded Types — Validate at Boundaries

type ValidatedEmail = string & { readonly __brand: 'ValidatedEmail' };
type UserId = string & { readonly __brand: 'UserId' };
function validateEmail(input: string): ValidatedEmail | null {
return isValidEmail(input) ? (input as ValidatedEmail) : null;
}
// Type system prevents using unvalidated data
function sendEmail(to: ValidatedEmail, subject: string) {
// No need to re-validate — type guarantees validity
}

Once you have a ValidatedEmail, downstream functions carry zero validation overhead. The type system encodes the knowledge that validation occurred.

Error Principles

  1. Never silently swallow errors — log or propagate, never ignore
  2. Fail fast at boundaries — validate inputs immediately, not deep in call stack
  3. Provide actionable messages — what failed, expected vs actual, how to fix
// ✅ Actionable error with context
throw new ValidationError(
`Email validation failed for "user_email": ` +
`Expected "name@domain.com", received "${input}". ` +
`Use validateEmailFormat() to check before calling.`
);
// ❌ Opaque
throw new Error("Validation failed");

File & Module Organization

Structure with Clear Boundaries

// ========================================
// PUBLIC API
// ========================================
export class UserService {
constructor(private readonly db: Database) {}
async createUser(data: CreateUserData): Promise<Result<User, CreateError>> {
// Public interface
}
}
// ========================================
// VALIDATION
// ========================================
function validateUserData(data: unknown): Result<ValidatedData, ValidationError> {
// Grouped validation logic
}
// ========================================
// PRIVATE HELPERS
// ========================================
function hashPassword(password: string): Promise<HashedPassword> {
// Internal implementation
}

Organization Rules

  1. Group by feature/domain, not file type — authentication/, orders/, payments/
  2. Public API first — exported functions at top, helpers at bottom
  3. One major export per fileUserService.ts exports UserService
  4. Co-locate testsUserService.test.ts next to UserService.ts
  5. 300-line guideline — not a hard limit, but a refactoring trigger
  6. Minimal cross-module dependencies — each module is a clean context boundary
project/
├── authentication/ # Self-contained context
│ ├── index.ts # Public API only
│ ├── credentials.ts
│ ├── sessions.ts
│ └── README.md # Module architecture
├── orders/ # Independent context
└── storage/ # Independent context

Testing

Testing Trophy — Mostly Integration

"Write tests. Not too many. Mostly integration." — Kent C. Dodds

  1. Static Analysis (foundation): TypeScript strict mode, ESLint
  2. Unit Tests (narrow): Pure functions, complex algorithms
  3. Integration Tests (widest — most tests here): How pieces work together, where bugs actually live
  4. E2E Tests (top): Critical user journeys only

Tests as Documentation

Test names describe scenarios. Docstrings explain "why." Tests demonstrate usage.

test('should reject invalid credentials without revealing if username exists', async () => {
// Prevents username enumeration attacks
const auth = new Authenticator(database);
const result = await auth.authenticate({
email: 'nonexistent@example.com',
password: 'any-password'
});
expect(result.ok).toBe(false);
expect(result.error.code).toBe('INVALID_CREDENTIALS');
expect(result.error.message).not.toContain('user not found');
});

Testing Rules

  1. Test behavior, not implementation — focus on inputs/outputs, not internal state
  2. One concept per test — don't test multiple unrelated things
  3. Integration over unit — test pieces working together (more confidence per test, more resilient to refactoring)
  4. Clear test names — describe the scenario: test('user can add items to cart')
  5. 80% coverage minimum — focus on critical paths

Observability

Structured Logging

// ✅ Structured — queryable, correlated
logger.info('Request processed', {
request_id: requestId,
user_id: userId,
endpoint: req.path,
method: req.method,
duration_ms: duration,
status_code: res.statusCode,
cache_hit: cacheHit
});
// ❌ Unstructured — hard to query
logger.info(`User ${userId} accessed ${req.path}`);

What to Log

Always include: request_id, user_id, trace_id, entity IDs, operation type, duration_ms, error details.

Log at critical boundaries:

  • External API calls (request/response)
  • Database operations (query, duration)
  • Authentication/authorization decisions
  • Error occurrences with full context

One structured event per operation — derive metrics, logs, or traces from the same data. Don't instrument separately for each observability pillar.

Agentic Coding Patterns

These patterns address the unique demands of code that will be read, modified, and executed by AI agents alongside humans.

Idempotent Operations

Agents retry. Network calls fail. Tasks get re-run. Design every mutation to be safely repeatable.

// ✅ Idempotent — safe to retry
async function ensureUserExists(
email: ValidatedEmail,
db: Database
): Promise<User> {
const existing = await db.users.findByEmail(email);
if (existing) return existing;
return db.users.create({ email });
}
// ❌ Non-idempotent — duplicates on retry
async function createUser(email: string, db: Database): Promise<User> {
return db.users.create({ email });
}

Explicit State Machines Over Implicit Flows

When operations have distinct phases, model them explicitly. Agents reason about state machines far better than implicit status flags scattered across objects.

type OrderState =
| { status: 'draft'; items: Item[] }
| { status: 'submitted'; items: Item[]; submittedAt: DateTime }
| { status: 'paid'; items: Item[]; submittedAt: DateTime; paymentId: string }
| { status: 'shipped'; items: Item[]; trackingNumber: string };
// Each transition is a pure function with clear preconditions
function submitOrder(order: OrderState & { status: 'draft' }): OrderState & { status: 'submitted' } {
return { ...order, status: 'submitted', submittedAt: DateTime.now() };
}

Machine-Parseable Errors

Agents need structured errors alongside human-readable ones. Return error codes that can be programmatically matched, with messages that explain context.

type AppError = {
code: 'VALIDATION_FAILED' | 'NOT_FOUND' | 'CONFLICT' | 'UPSTREAM_TIMEOUT';
message: string; // Human-readable explanation
field?: string; // Which input caused it
retryable: boolean; // Can the caller retry?
};

Atomic, Independently-Verifiable Changes

Structure work so each change can be validated in isolation. This applies to commits, PRs, and function design. An agent (or reviewer) should be able to verify correctness without understanding the entire system.

// ✅ Each function is independently testable and verifiable
function parseConfig(raw: string): Result<Config, ParseError> { /* ... */ }
function validateConfig(config: Config): Result<ValidConfig, ValidationError[]> { /* ... */ }
function applyConfig(config: ValidConfig, system: System): Result<void, ApplyError> { /* ... */ }
// ❌ Monolithic — must understand everything to verify anything
function loadAndApplyConfig(path: string): void { /* 200 lines */ }

Convention Over Configuration

Reduce the search space for agents (and humans). Consistent patterns mean less context needed per decision.

  • Consistent file naming: UserService.ts, UserService.test.ts, UserService.types.ts
  • Predictable directory structure across features
  • Standard patterns for CRUD operations, API endpoints, error handling
  • If your project has a pattern, follow it. If it doesn't, establish one and document it

Contract-First Design

Define types before implementation. Types are the cheapest, most scannable form of documentation. An agent reading your types understands your system's data flow without reading a single function body.

// Define the contract first
interface OrderService {
create(data: CreateOrderInput): Promise<Result<Order, CreateOrderError>>;
cancel(id: OrderId, reason: CancelReason): Promise<Result<void, CancelError>>;
findByUser(userId: UserId, pagination: Pagination): Promise<PaginatedResult<OrderSummary>>;
}
// Then implement — the types guide everything

Observable Side Effects

Every mutation should produce structured output describing what changed. This enables agents to verify their actions and enables humans to audit.

type MutationResult<T> = {
data: T;
changes: Change[]; // What was modified
warnings: string[]; // Non-fatal issues encountered
};
async function updateUserProfile(
id: UserId,
updates: ProfileUpdates
): Promise<Result<MutationResult<UserProfile>, UpdateError>> {
// Returns both the result AND a description of what changed
}

Context Optimisation & Token Economics

Every token an agent reads is a token it can't use for reasoning. Treat context like memory in an embedded system — budget it, measure it, and refuse to waste it.

The Context Budget

AI agents operate within fixed context windows. Your code, documentation, error messages, and tool outputs all compete for the same finite space. Code that is token-efficient isn't just neat — it directly improves agent reasoning quality.

Context Window (finite)
├── System prompt & instructions ~2-5k tokens (fixed cost)
├── Conversation history ~variable
├── Tool definitions ~1-10k tokens (per tool schema)
├── Retrieved code / docs ~variable ← YOU CONTROL THIS
├── Agent reasoning ~variable ← THIS GETS SQUEEZED
└── Output generation ~variable ← AND SO DOES THIS
The more tokens your code consumes, the less room the agent has to think. Optimise ruthlessly.

Semantic Compression

Collapse granular interfaces into high-level semantic operations. Instead of exposing every low-level action, expose intent-based APIs.

// ❌ 15 granular tools = ~15k tokens of schema// An agent must read and reason about ALL of them
tools: [createFile,readFile,deleteFile,moveFile,copyFile,listDirectory,createDirectory,deleteDirectory,getFileMetadata,setFilePermissions,watchFile,compressFile,decompressFile,hashFile,diffFiles]// ✅ 1 semantic dispatcher = ~1k tokens of schema// Agent reasons about intent, not mechanics
tools: [{name: "filesystem",description: "Manage files and directories",parameters: {operation: "create | read | delete | move | copy | list | ...",path: "string",options: "object (operation-specific)"}}]

This is the dispatcher pattern: consolidate related tools behind a single entry point that routes by intent. Token cost drops dramatically while functionality stays the same.

Layered Context Loading

Don't front-load everything. Provide summaries first, with drill-down paths for when the agent actually needs more detail.

// ✅ Layered: summary first, details on demandfunctiongetProjectOverview(): ProjectSummary{return{name: "DataPipeline",modules: ["ingestion","transform","export"],entryPoint: "src/main.ts",recentChanges: getRecentChangeSummary(5),// Drill-down references — agent only loads what it needsgetModuleDetail: (name: string)=>loadModuleContext(name),getFileContent: (path: string)=>loadFileContext(path),};}// ❌ Eager: dumps everything into context upfrontfunctiongetProjectContext(): FullProjectDump{return{allFiles: readAllFiles(),// 50k tokensallTests: readAllTests(),// 30k tokensallDocs: readAllDocs(),// 20k tokens// Agent's context window is now full before it starts thinking};}

Token-Aware Documentation

Write documentation that serves both human readers and token budgets. Every word should earn its place.

# ❌ Token-heavy: narrative style, repetitive, verbose## Overview of the Authentication Module
The authentication module is responsible for handling all aspects of user authentication within our application. This module was designed with security best practices in mind and implements industry-standard protocols. The module handles user login, token generation, session management, and token refresh functionality. It is important to note that this module uses JWT tokens for authentication purposes.
(~80 tokens to say what could be said in 15)
# ✅ Token-efficient: dense, scannable, no filler## Authentication
JWT-based auth with refresh token rotation.
- Entry: `authenticate()``Result<Session, AuthError>`- Tokens: 15min access, 7d refresh (HTTP-only cookie)
- Storage: PostgreSQL users, Redis token blacklist
(~40 tokens, more information conveyed)

Structured Output for Agent Consumption

When building tools or functions that agents will consume, prefer structured, parseable output over human-readable prose.

// ✅ Agent-friendly: structured, parseable, minimaltypeBuildResult={success: boolean;errors: {file: string;line: number;code: string;message: string}[];warnings: {file: string;line: number;code: string;message: string}[];stats: {duration_ms: number;filesProcessed: number};};// ❌ Human-only: requires parsing natural languagefunctiongetBuildOutput(): string{return`Build completed with 2 errors and 1 warning. Error in src/auth.ts line 42: Type 'string' is not assignable... Error in src/orders.ts line 18: Property 'id' does not exist... Warning in src/utils.ts line 7: Unused variable 'temp'... Build took 3.2 seconds, processed 47 files.`;}

Context Boundaries as Architecture

Design modules so an agent can work within one module without loading others. Each module should be a self-contained context unit.

// ✅ Clean context boundary — agent only needs this module// payments/index.tsexportinterfacePaymentService{charge(input: ChargeInput): Promise<Result<Payment,PaymentError>>;refund(id: PaymentId,reason: RefundReason): Promise<Result<Refund,RefundError>>;}// payments/types.ts — all types co-located, no external dependenciesexporttypeChargeInput={amount: Money;method: PaymentMethod;idempotencyKey: string;// Agent-friendly: built-in retry safety};// payments/errors.ts — exhaustive, machine-readableexporttypePaymentError=|{code: 'INSUFFICIENT_FUNDS';available: Money}|{code: 'CARD_DECLINED';reason: string;retryable: false}|{code: 'GATEWAY_TIMEOUT';retryable: true};
// ❌ Leaky context boundary — agent must load 4 modules to understand 1
// payments/index.ts
import { User } from '../users/types';
import { Order } from '../orders/types';
import { AuditLogger } from '../audit/logger';
import { ConfigManager } from '../config/manager';
// Agent now needs context from users/, orders/, audit/, config/

Compression Strategies Reference

StrategyBeforeAfterSavings
Semantic dispatchersN tool schemas (~N × 1k tokens)1 dispatcher (~1k tokens)~(N-1)k tokens
Layered loadingFull dump (50k tokens)Summary + drill-down (2k + on-demand)~48k idle tokens
Dense docsNarrative prose (~80 tokens/concept)Structured bullets (~40 tokens/concept)~50%
Co-located typesScattered across modulesSingle types.ts per moduleFewer file loads
Summary-first returnsFull object graphsSummary + reference IDs60-90% per call
Discriminated unionsGeneric error + message stringTyped union with code fieldEliminates parsing

Project Navigation

CLAUDE.md at Project Root

Every project needs a navigation file. List entry points, patterns, and common tasks.

# Project: Data Processing Pipeline
## Entry Points
- `src/main.ts`: CLI interface
- `src/api/server.ts`: REST API
- `src/processors/pipeline.ts`: Core processing
## Key Patterns
- All processors implement `Processor` interface (src/processors/base.ts)
- Config uses Zod schemas (src/config/schemas.ts)
- External APIs via `APIClient` (src/external/client.ts)
## Common Tasks
- Add data source → implement `DataSource` in `src/api/sources/`
- Add transformation → implement `Transformer` in `src/processors/transformers/`

Keep under 200 lines. Update when architecture changes.

Module-Level READMEs

Every major directory gets a README answering: What is this? How does it work? What are the gotchas?

# Module: User Authentication
## Purpose
JWT-based authentication with refresh token rotation
## Key Decisions
- bcrypt cost factor 12 for password hashing
- Access tokens expire after 15 minutes
- Refresh tokens stored in HTTP-only cookies
## Dependencies
- jose library for JWT (not jsonwebtoken — more secure)
- PostgreSQL for user storage
- Redis for token blacklist

Progressive Context Hierarchy

  1. CLAUDE.md / README.md at root — system overview, entry points, setup
  2. README.md per major module — module purpose, key decisions, patterns
  3. Section comments in files — group related code with clear headers
  4. Function/class docs — purpose, examples for non-obvious APIs
  5. Inline comments — only for "why" decisions

Anti-Patterns

  • Premature optimization — Measure first, optimize second
  • Hasty abstractions — Wait for 3rd duplication before extracting
  • Clever code — Simple and obvious beats clever and compact
  • Silent failures — Log and propagate, never swallow
  • Vague interfacesprocess(data: any): any provides zero guidance
  • Hidden dependencies — Global state, singletons, ambient imports
  • Nested conditionals — Use guard clauses instead
  • Comments describing "what" — If you need a comment to explain what code does, rename things
  • Premature generalization — Build for today's requirements, not hypothetical futures
  • Token bloat — Functions returning everything when callers need summaries
  • Inverted disclosure — Helpers at top, public API buried at bottom
  • Flat files — 500-line files with no visual hierarchy, grouping, or section comments
  • Leaky context boundaries — Modules that import heavily from siblings, forcing agents to load the entire codebase
  • Eager context loading — Dumping full project state into agent context when a summary would suffice

Checklist

Before submitting code:

  • Solves the stated problem with minimal code?
  • A new developer can understand it without extensive context?
  • Errors handled with actionable messages?
  • Names clear, specific, and unambiguous?
  • Functions have single, clear responsibilities?
  • Dependencies explicit (no hidden global state)?
  • Tests cover critical paths?
  • Operations idempotent where applicable?
  • Types define contracts before implementation?
  • Would this work well with ~200 lines of surrounding context?
  • Can an agent understand this module without loading adjacent modules?
  • Are public APIs scannable in under 50 lines?
  • Do tool/function outputs use structured types, not prose?
  • Is documentation token-dense (no filler words, no repetition)?
  • Does the file follow progressive disclosure (types → public API → helpers)?

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

About

A quick reference to the living codestyle guidelines I feed to my AI agents, to help maintain scalable repositories.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

(See CLAUDE.md for sharper context-saving alternative)

Code Style Guide

Core Principle: Context is finite. Every token — code, comment, structure — competes for limited attention. Maximize signal, minimize noise. Write for two audiences: humans with limited working memory and AI agents with bounded context windows.

Philosophy

The optimal code is the minimum necessary to solve the problem correctly. Every additional line is debt.

Progressive Disclosure: Structure code layer-by-layer. Readers grasp high-level flow immediately, drilling into details only when needed. File names indicate purpose. Directory structures mirror conceptual hierarchies. Function names describe behavior without reading implementation. See Progressive Disclosure for concrete patterns.

Self-Documenting: Names eliminate need for comments. Comments explain "why," never "what." If you chose algorithm A over B for subtle reasons, state that. If you're working around a library bug, explain it.

Aggressive Minimalism: Before adding code, ask: "Is this the simplest solution?" Before adding a comment: "Does this clarify something non-obvious?" Before introducing an abstraction: "Does this reduce complexity, or merely relocate it?"

AHA Over DRY: Avoid Hasty Abstractions. Wait for the 3rd duplication before extracting. The wrong abstraction is worse than duplication. Three similar lines of code is better than a premature abstraction.

Progressive Disclosure

Structure every layer of your system so readers — human or agent — get the right level of detail at the right time. No one should need to read 2000 lines to understand what a module does.

The Zoom Principle

Code should work like a map: zoom out for the big picture, zoom in for street-level detail. Each zoom level should be self-sufficient.

// Level 0: Directory structure tells you what exists
src/
├── authentication/ # "There's an auth system"
├── orders/ # "There's an order system" ├── payments/ # "There's a payment system"
└── README.md # How they connect
// Level 1: Index file tells you what it can do
// authentication/index.ts
export { authenticateUser } from './authenticate';
export { refreshSession } from './sessions';
export { revokeAccess } from './revoke';
// No implementation visible — just capabilities
// Level 2: Function signature tells you the contract
async function authenticateUser(
credentials: UserCredentials,
db: Database,
clock: Clock
): Promise<Result<AuthSession, AuthError>>
// Level 3: Implementation tells you how
// Only read this when you need to change the behaviour

File-Level Disclosure

Every file should answer "what is this?" in its first 10 lines. Implementation details belong below.

// ✅ Top of file reveals purpose, contract, and shape/** * Order Processing Pipeline *  * Validates → enriches → prices → submits orders. * Entry point: processOrder() * Error strategy: Result types, no throws */// Types first — the contracttypeProcessOrderInput={/* ... */};typeProcessOrderResult=Result<Receipt,ProcessError>;// Public API secondexportasyncfunctionprocessOrder(input: ProcessOrderInput): Promise<ProcessOrderResult>{constvalidated=validateOrder(input);if(!validated.ok)returnvalidated;constenriched=awaitenrichWithInventory(validated.value);if(!enriched.ok)returnenriched;returnsubmitOrder(enriched.value);}// Private helpers last — only read if you need to understand a specific stepfunctionvalidateOrder(input: ProcessOrderInput): Result<ValidatedOrder,ProcessError>{// ...}
// ❌ Implementation soup — must read everything to understand anythingimport{db}from'../globals';constRETRY_COUNT=3;constBACKOFF_MS=100;functionhelper1(){/* ... */}functionhelper2(){/* ... */}// 200 lines later...exportfunctionprocessOrder(){/* ... */}

Documentation Disclosure

Match documentation depth to the reader's likely intent. Most readers want "what does this do?" — very few want "why did you choose bcrypt over argon2?"

Level 1 — CLAUDE.md (5 seconds)
"This is an order processing API. Entry: src/api/server.ts"
Level 2 — Module README (30 seconds) "Orders go through validate → enrich → price → submit. Uses Result types. Retries on transient failures."
Level 3 — Section comments (2 minutes)
// ========================================
// PRICING ENGINE
// ========================================
// Applies tiered discounts, tax rules, and currency conversion.
// See: docs/pricing-model.md for business rules.
Level 4 — Inline "why" comments (as needed)
// Using ceiling division here because partial units // must be billed as full units per the SLA.

API & Type Disclosure

Public interfaces should be scannable summaries. Implementation types stay internal.

// ✅ Public types: minimal, focused, scannable// orders/types.ts — what consumers need to knowexporttypeOrderSummary={id: OrderId;status: OrderStatus;total: Money;itemCount: number;createdAt: DateTime;};// orders/internal-types.ts — implementation detail// Not exported. Contains pricing breakdowns, audit trails,// intermediate computation states, retry metadata, etc.typeOrderPricingContext={/* ... */};typeOrderAuditEntry={/* ... */};

Disclosure Anti-Patterns

  • Premature depth: Putting implementation details in README files
  • Flat disclosure: 500-line files with no visual hierarchy or grouping
  • Inverted disclosure: Helpers at top, public API buried at bottom
  • Missing levels: Jumping from directory listing straight to inline comments with nothing in between

Naming

The #1 impact on readability. Good names eliminate mental translation overhead.

// ✅ Descriptive, unambiguous
async function validateJsonAgainstSchema(
schema: ZodSchema,
input: string
): Promise<ValidationResult>
function calculateExponentialBackoff(
attemptNumber: number,
baseDelayMs: number
): number
// ❌ Vague, abbreviated
async function valJson(s: any, i: string): Promise<any>
function calcBackoff(n: number, d: number): number

Rules:

  1. Be specific: activeUsers not users, httpTimeoutMs not timeout
  2. Include units: delayMs not delay, maxRetries not max
  3. Avoid abbreviations: customer not cust, configuration not cfg
  4. Use domain language: Names from business domain, not technical abstractions
  5. Boolean prefixes: isValid, hasPermission, canEdit, shouldRetry
  6. Verbs for functions: validateEmailFormat() not checkEmail(), fetchActiveUsers() not getUsers()

Function Design

Single Responsibility with Explicit Contracts

// ✅ Self-contained, explicit dependencies, typed contract
async function authenticateUser(
credentials: UserCredentials,
database: Database,
currentTime: DateTime
): Promise<Result<AuthSession, AuthError>> {
// All dependencies visible in signature
// Return type reveals all possible outcomes
}
// ❌ Hidden dependencies, unclear contract
async function auth(data: any): Promise<any> {
// Uses global config, modifies global state
}

Guard Clauses Over Nesting

Handle edge cases first, keep the happy path unindented and visible.

// ✅ Guard clauses — happy path clear
function processOrder(order: Order): Result<Receipt, ProcessError> {
if (!order) return err('missing_order');
if (order.items.length === 0) return err('empty_order');
if (order.total <= 0) return err('invalid_total');
if (!order.paymentMethod) return err('missing_payment');
return ok(completePayment(order));
}
// ❌ Nested conditions — happy path buried
function processOrder(order: Order) {
if (order) {
if (order.items.length > 0) {
if (order.total > 0) {
// Happy path buried 4 levels deep
}
}
}
}

Design Rules

  1. Single responsibility — describable in one sentence
  2. Explicit dependencies — all inputs as parameters, no hidden global state
  3. Type everything — TypeScript strict mode, Python type hints
  4. Self-contained context units — comprehensible without reading other files
  5. 50-line guideline — not a hard limit, but a refactoring trigger

Error Handling

Result Types — Make Errors Explicit

Errors belong in function signatures, not hidden behind throw.

type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
type UserError = 'not_found' | 'unauthorized' | 'network_failure';
async function fetchUser(id: string): Promise<Result<User, UserError>> {
// Errors are part of the contract
}
// Usage forces error handling — compiler catches missing cases
const result = await fetchUser(userId);
if (!result.ok) {
switch (result.error) {
case 'not_found': return show404();
case 'unauthorized': return redirectLogin();
case 'network_failure': return showRetry();
}
}

When to use Result types: API calls, file I/O, validation, any complex error path. When to use exceptions: Truly exceptional/unrecoverable situations (out of memory, corrupted state).

Branded Types — Validate at Boundaries

type ValidatedEmail = string & { readonly __brand: 'ValidatedEmail' };
type UserId = string & { readonly __brand: 'UserId' };
function validateEmail(input: string): ValidatedEmail | null {
return isValidEmail(input) ? (input as ValidatedEmail) : null;
}
// Type system prevents using unvalidated data
function sendEmail(to: ValidatedEmail, subject: string) {
// No need to re-validate — type guarantees validity
}

Once you have a ValidatedEmail, downstream functions carry zero validation overhead. The type system encodes the knowledge that validation occurred.

Error Principles

  1. Never silently swallow errors — log or propagate, never ignore
  2. Fail fast at boundaries — validate inputs immediately, not deep in call stack
  3. Provide actionable messages — what failed, expected vs actual, how to fix
// ✅ Actionable error with context
throw new ValidationError(
`Email validation failed for "user_email": ` +
`Expected "name@domain.com", received "${input}". ` +
`Use validateEmailFormat() to check before calling.`
);
// ❌ Opaque
throw new Error("Validation failed");

File & Module Organization

Structure with Clear Boundaries

// ========================================
// PUBLIC API
// ========================================
export class UserService {
constructor(private readonly db: Database) {}
async createUser(data: CreateUserData): Promise<Result<User, CreateError>> {
// Public interface
}
}
// ========================================
// VALIDATION
// ========================================
function validateUserData(data: unknown): Result<ValidatedData, ValidationError> {
// Grouped validation logic
}
// ========================================
// PRIVATE HELPERS
// ========================================
function hashPassword(password: string): Promise<HashedPassword> {
// Internal implementation
}

Organization Rules

  1. Group by feature/domain, not file type — authentication/, orders/, payments/
  2. Public API first — exported functions at top, helpers at bottom
  3. One major export per fileUserService.ts exports UserService
  4. Co-locate testsUserService.test.ts next to UserService.ts
  5. 300-line guideline — not a hard limit, but a refactoring trigger
  6. Minimal cross-module dependencies — each module is a clean context boundary
project/
├── authentication/ # Self-contained context
│ ├── index.ts # Public API only
│ ├── credentials.ts
│ ├── sessions.ts
│ └── README.md # Module architecture
├── orders/ # Independent context
└── storage/ # Independent context

Testing

Testing Trophy — Mostly Integration

"Write tests. Not too many. Mostly integration." — Kent C. Dodds

  1. Static Analysis (foundation): TypeScript strict mode, ESLint
  2. Unit Tests (narrow): Pure functions, complex algorithms
  3. Integration Tests (widest — most tests here): How pieces work together, where bugs actually live
  4. E2E Tests (top): Critical user journeys only

Tests as Documentation

Test names describe scenarios. Docstrings explain "why." Tests demonstrate usage.

test('should reject invalid credentials without revealing if username exists', async () => {
// Prevents username enumeration attacks
const auth = new Authenticator(database);
const result = await auth.authenticate({
email: 'nonexistent@example.com',
password: 'any-password'
});
expect(result.ok).toBe(false);
expect(result.error.code).toBe('INVALID_CREDENTIALS');
expect(result.error.message).not.toContain('user not found');
});

Testing Rules

  1. Test behavior, not implementation — focus on inputs/outputs, not internal state
  2. One concept per test — don't test multiple unrelated things
  3. Integration over unit — test pieces working together (more confidence per test, more resilient to refactoring)
  4. Clear test names — describe the scenario: test('user can add items to cart')
  5. 80% coverage minimum — focus on critical paths

Observability

Structured Logging

// ✅ Structured — queryable, correlated
logger.info('Request processed', {
request_id: requestId,
user_id: userId,
endpoint: req.path,
method: req.method,
duration_ms: duration,
status_code: res.statusCode,
cache_hit: cacheHit
});
// ❌ Unstructured — hard to query
logger.info(`User ${userId} accessed ${req.path}`);

What to Log

Always include: request_id, user_id, trace_id, entity IDs, operation type, duration_ms, error details.

Log at critical boundaries:

  • External API calls (request/response)
  • Database operations (query, duration)
  • Authentication/authorization decisions
  • Error occurrences with full context

One structured event per operation — derive metrics, logs, or traces from the same data. Don't instrument separately for each observability pillar.

Agentic Coding Patterns

These patterns address the unique demands of code that will be read, modified, and executed by AI agents alongside humans.

Idempotent Operations

Agents retry. Network calls fail. Tasks get re-run. Design every mutation to be safely repeatable.

// ✅ Idempotent — safe to retry
async function ensureUserExists(
email: ValidatedEmail,
db: Database
): Promise<User> {
const existing = await db.users.findByEmail(email);
if (existing) return existing;
return db.users.create({ email });
}
// ❌ Non-idempotent — duplicates on retry
async function createUser(email: string, db: Database): Promise<User> {
return db.users.create({ email });
}

Explicit State Machines Over Implicit Flows

When operations have distinct phases, model them explicitly. Agents reason about state machines far better than implicit status flags scattered across objects.

type OrderState =
| { status: 'draft'; items: Item[] }
| { status: 'submitted'; items: Item[]; submittedAt: DateTime }
| { status: 'paid'; items: Item[]; submittedAt: DateTime; paymentId: string }
| { status: 'shipped'; items: Item[]; trackingNumber: string };
// Each transition is a pure function with clear preconditions
function submitOrder(order: OrderState & { status: 'draft' }): OrderState & { status: 'submitted' } {
return { ...order, status: 'submitted', submittedAt: DateTime.now() };
}

Machine-Parseable Errors

Agents need structured errors alongside human-readable ones. Return error codes that can be programmatically matched, with messages that explain context.

type AppError = {
code: 'VALIDATION_FAILED' | 'NOT_FOUND' | 'CONFLICT' | 'UPSTREAM_TIMEOUT';
message: string; // Human-readable explanation
field?: string; // Which input caused it
retryable: boolean; // Can the caller retry?
};

Atomic, Independently-Verifiable Changes

Structure work so each change can be validated in isolation. This applies to commits, PRs, and function design. An agent (or reviewer) should be able to verify correctness without understanding the entire system.

// ✅ Each function is independently testable and verifiable
function parseConfig(raw: string): Result<Config, ParseError> { /* ... */ }
function validateConfig(config: Config): Result<ValidConfig, ValidationError[]> { /* ... */ }
function applyConfig(config: ValidConfig, system: System): Result<void, ApplyError> { /* ... */ }
// ❌ Monolithic — must understand everything to verify anything
function loadAndApplyConfig(path: string): void { /* 200 lines */ }

Convention Over Configuration

Reduce the search space for agents (and humans). Consistent patterns mean less context needed per decision.

  • Consistent file naming: UserService.ts, UserService.test.ts, UserService.types.ts
  • Predictable directory structure across features
  • Standard patterns for CRUD operations, API endpoints, error handling
  • If your project has a pattern, follow it. If it doesn't, establish one and document it

Contract-First Design

Define types before implementation. Types are the cheapest, most scannable form of documentation. An agent reading your types understands your system's data flow without reading a single function body.

// Define the contract first
interface OrderService {
create(data: CreateOrderInput): Promise<Result<Order, CreateOrderError>>;
cancel(id: OrderId, reason: CancelReason): Promise<Result<void, CancelError>>;
findByUser(userId: UserId, pagination: Pagination): Promise<PaginatedResult<OrderSummary>>;
}
// Then implement — the types guide everything

Observable Side Effects

Every mutation should produce structured output describing what changed. This enables agents to verify their actions and enables humans to audit.

type MutationResult<T> = {
data: T;
changes: Change[]; // What was modified
warnings: string[]; // Non-fatal issues encountered
};
async function updateUserProfile(
id: UserId,
updates: ProfileUpdates
): Promise<Result<MutationResult<UserProfile>, UpdateError>> {
// Returns both the result AND a description of what changed
}

Context Optimisation & Token Economics

Every token an agent reads is a token it can't use for reasoning. Treat context like memory in an embedded system — budget it, measure it, and refuse to waste it.

The Context Budget

AI agents operate within fixed context windows. Your code, documentation, error messages, and tool outputs all compete for the same finite space. Code that is token-efficient isn't just neat — it directly improves agent reasoning quality.

Context Window (finite)
├── System prompt & instructions ~2-5k tokens (fixed cost)
├── Conversation history ~variable
├── Tool definitions ~1-10k tokens (per tool schema)
├── Retrieved code / docs ~variable ← YOU CONTROL THIS
├── Agent reasoning ~variable ← THIS GETS SQUEEZED
└── Output generation ~variable ← AND SO DOES THIS
The more tokens your code consumes, the less room the agent has to think. Optimise ruthlessly.

Semantic Compression

Collapse granular interfaces into high-level semantic operations. Instead of exposing every low-level action, expose intent-based APIs.

// ❌ 15 granular tools = ~15k tokens of schema// An agent must read and reason about ALL of them
tools: [createFile,readFile,deleteFile,moveFile,copyFile,listDirectory,createDirectory,deleteDirectory,getFileMetadata,setFilePermissions,watchFile,compressFile,decompressFile,hashFile,diffFiles]// ✅ 1 semantic dispatcher = ~1k tokens of schema// Agent reasons about intent, not mechanics
tools: [{name: "filesystem",description: "Manage files and directories",parameters: {operation: "create | read | delete | move | copy | list | ...",path: "string",options: "object (operation-specific)"}}]

This is the dispatcher pattern: consolidate related tools behind a single entry point that routes by intent. Token cost drops dramatically while functionality stays the same.

Layered Context Loading

Don't front-load everything. Provide summaries first, with drill-down paths for when the agent actually needs more detail.

// ✅ Layered: summary first, details on demandfunctiongetProjectOverview(): ProjectSummary{return{name: "DataPipeline",modules: ["ingestion","transform","export"],entryPoint: "src/main.ts",recentChanges: getRecentChangeSummary(5),// Drill-down references — agent only loads what it needsgetModuleDetail: (name: string)=>loadModuleContext(name),getFileContent: (path: string)=>loadFileContext(path),};}// ❌ Eager: dumps everything into context upfrontfunctiongetProjectContext(): FullProjectDump{return{allFiles: readAllFiles(),// 50k tokensallTests: readAllTests(),// 30k tokensallDocs: readAllDocs(),// 20k tokens// Agent's context window is now full before it starts thinking};}

Token-Aware Documentation

Write documentation that serves both human readers and token budgets. Every word should earn its place.

# ❌ Token-heavy: narrative style, repetitive, verbose## Overview of the Authentication Module
The authentication module is responsible for handling all aspects of user authentication within our application. This module was designed with security best practices in mind and implements industry-standard protocols. The module handles user login, token generation, session management, and token refresh functionality. It is important to note that this module uses JWT tokens for authentication purposes.
(~80 tokens to say what could be said in 15)
# ✅ Token-efficient: dense, scannable, no filler## Authentication
JWT-based auth with refresh token rotation.
- Entry: `authenticate()``Result<Session, AuthError>`- Tokens: 15min access, 7d refresh (HTTP-only cookie)
- Storage: PostgreSQL users, Redis token blacklist
(~40 tokens, more information conveyed)

Structured Output for Agent Consumption

When building tools or functions that agents will consume, prefer structured, parseable output over human-readable prose.

// ✅ Agent-friendly: structured, parseable, minimaltypeBuildResult={success: boolean;errors: {file: string;line: number;code: string;message: string}[];warnings: {file: string;line: number;code: string;message: string}[];stats: {duration_ms: number;filesProcessed: number};};// ❌ Human-only: requires parsing natural languagefunctiongetBuildOutput(): string{return`Build completed with 2 errors and 1 warning. Error in src/auth.ts line 42: Type 'string' is not assignable... Error in src/orders.ts line 18: Property 'id' does not exist... Warning in src/utils.ts line 7: Unused variable 'temp'... Build took 3.2 seconds, processed 47 files.`;}

Context Boundaries as Architecture

Design modules so an agent can work within one module without loading others. Each module should be a self-contained context unit.

// ✅ Clean context boundary — agent only needs this module// payments/index.tsexportinterfacePaymentService{charge(input: ChargeInput): Promise<Result<Payment,PaymentError>>;refund(id: PaymentId,reason: RefundReason): Promise<Result<Refund,RefundError>>;}// payments/types.ts — all types co-located, no external dependenciesexporttypeChargeInput={amount: Money;method: PaymentMethod;idempotencyKey: string;// Agent-friendly: built-in retry safety};// payments/errors.ts — exhaustive, machine-readableexporttypePaymentError=|{code: 'INSUFFICIENT_FUNDS';available: Money}|{code: 'CARD_DECLINED';reason: string;retryable: false}|{code: 'GATEWAY_TIMEOUT';retryable: true};
// ❌ Leaky context boundary — agent must load 4 modules to understand 1
// payments/index.ts
import { User } from '../users/types';
import { Order } from '../orders/types';
import { AuditLogger } from '../audit/logger';
import { ConfigManager } from '../config/manager';
// Agent now needs context from users/, orders/, audit/, config/

Compression Strategies Reference

StrategyBeforeAfterSavings
Semantic dispatchersN tool schemas (~N × 1k tokens)1 dispatcher (~1k tokens)~(N-1)k tokens
Layered loadingFull dump (50k tokens)Summary + drill-down (2k + on-demand)~48k idle tokens
Dense docsNarrative prose (~80 tokens/concept)Structured bullets (~40 tokens/concept)~50%
Co-located typesScattered across modulesSingle types.ts per moduleFewer file loads
Summary-first returnsFull object graphsSummary + reference IDs60-90% per call
Discriminated unionsGeneric error + message stringTyped union with code fieldEliminates parsing

Project Navigation

CLAUDE.md at Project Root

Every project needs a navigation file. List entry points, patterns, and common tasks.

# Project: Data Processing Pipeline
## Entry Points
- `src/main.ts`: CLI interface
- `src/api/server.ts`: REST API
- `src/processors/pipeline.ts`: Core processing
## Key Patterns
- All processors implement `Processor` interface (src/processors/base.ts)
- Config uses Zod schemas (src/config/schemas.ts)
- External APIs via `APIClient` (src/external/client.ts)
## Common Tasks
- Add data source → implement `DataSource` in `src/api/sources/`
- Add transformation → implement `Transformer` in `src/processors/transformers/`

Keep under 200 lines. Update when architecture changes.

Module-Level READMEs

Every major directory gets a README answering: What is this? How does it work? What are the gotchas?

# Module: User Authentication
## Purpose
JWT-based authentication with refresh token rotation
## Key Decisions
- bcrypt cost factor 12 for password hashing
- Access tokens expire after 15 minutes
- Refresh tokens stored in HTTP-only cookies
## Dependencies
- jose library for JWT (not jsonwebtoken — more secure)
- PostgreSQL for user storage
- Redis for token blacklist

Progressive Context Hierarchy

  1. CLAUDE.md / README.md at root — system overview, entry points, setup
  2. README.md per major module — module purpose, key decisions, patterns
  3. Section comments in files — group related code with clear headers
  4. Function/class docs — purpose, examples for non-obvious APIs
  5. Inline comments — only for "why" decisions

Anti-Patterns

  • Premature optimization — Measure first, optimize second
  • Hasty abstractions — Wait for 3rd duplication before extracting
  • Clever code — Simple and obvious beats clever and compact
  • Silent failures — Log and propagate, never swallow
  • Vague interfacesprocess(data: any): any provides zero guidance
  • Hidden dependencies — Global state, singletons, ambient imports
  • Nested conditionals — Use guard clauses instead
  • Comments describing "what" — If you need a comment to explain what code does, rename things
  • Premature generalization — Build for today's requirements, not hypothetical futures
  • Token bloat — Functions returning everything when callers need summaries
  • Inverted disclosure — Helpers at top, public API buried at bottom
  • Flat files — 500-line files with no visual hierarchy, grouping, or section comments
  • Leaky context boundaries — Modules that import heavily from siblings, forcing agents to load the entire codebase
  • Eager context loading — Dumping full project state into agent context when a summary would suffice

Checklist

Before submitting code:

  • Solves the stated problem with minimal code?
  • A new developer can understand it without extensive context?
  • Errors handled with actionable messages?
  • Names clear, specific, and unambiguous?
  • Functions have single, clear responsibilities?
  • Dependencies explicit (no hidden global state)?
  • Tests cover critical paths?
  • Operations idempotent where applicable?
  • Types define contracts before implementation?
  • Would this work well with ~200 lines of surrounding context?
  • Can an agent understand this module without loading adjacent modules?
  • Are public APIs scannable in under 50 lines?
  • Do tool/function outputs use structured types, not prose?
  • Is documentation token-dense (no filler words, no repetition)?
  • Does the file follow progressive disclosure (types → public API → helpers)?

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

About

A quick reference to the living codestyle guidelines I feed to my AI agents, to help maintain scalable repositories.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

(See CLAUDE.md for sharper context-saving alternative)

Code Style Guide

Core Principle: Context is finite. Every token — code, comment, structure — competes for limited attention. Maximize signal, minimize noise. Write for two audiences: humans with limited working memory and AI agents with bounded context windows.

Philosophy

The optimal code is the minimum necessary to solve the problem correctly. Every additional line is debt.

Progressive Disclosure: Structure code layer-by-layer. Readers grasp high-level flow immediately, drilling into details only when needed. File names indicate purpose. Directory structures mirror conceptual hierarchies. Function names describe behavior without reading implementation. See Progressive Disclosure for concrete patterns.

Self-Documenting: Names eliminate need for comments. Comments explain "why," never "what." If you chose algorithm A over B for subtle reasons, state that. If you're working around a library bug, explain it.

Aggressive Minimalism: Before adding code, ask: "Is this the simplest solution?" Before adding a comment: "Does this clarify something non-obvious?" Before introducing an abstraction: "Does this reduce complexity, or merely relocate it?"

AHA Over DRY: Avoid Hasty Abstractions. Wait for the 3rd duplication before extracting. The wrong abstraction is worse than duplication. Three similar lines of code is better than a premature abstraction.

Progressive Disclosure

Structure every layer of your system so readers — human or agent — get the right level of detail at the right time. No one should need to read 2000 lines to understand what a module does.

The Zoom Principle

Code should work like a map: zoom out for the big picture, zoom in for street-level detail. Each zoom level should be self-sufficient.

// Level 0: Directory structure tells you what exists
src/
├── authentication/ # "There's an auth system"
├── orders/ # "There's an order system" ├── payments/ # "There's a payment system"
└── README.md # How they connect
// Level 1: Index file tells you what it can do
// authentication/index.ts
export { authenticateUser } from './authenticate';
export { refreshSession } from './sessions';
export { revokeAccess } from './revoke';
// No implementation visible — just capabilities
// Level 2: Function signature tells you the contract
async function authenticateUser(
credentials: UserCredentials,
db: Database,
clock: Clock
): Promise<Result<AuthSession, AuthError>>
// Level 3: Implementation tells you how
// Only read this when you need to change the behaviour

File-Level Disclosure

Every file should answer "what is this?" in its first 10 lines. Implementation details belong below.

// ✅ Top of file reveals purpose, contract, and shape/** * Order Processing Pipeline *  * Validates → enriches → prices → submits orders. * Entry point: processOrder() * Error strategy: Result types, no throws */// Types first — the contracttypeProcessOrderInput={/* ... */};typeProcessOrderResult=Result<Receipt,ProcessError>;// Public API secondexportasyncfunctionprocessOrder(input: ProcessOrderInput): Promise<ProcessOrderResult>{constvalidated=validateOrder(input);if(!validated.ok)returnvalidated;constenriched=awaitenrichWithInventory(validated.value);if(!enriched.ok)returnenriched;returnsubmitOrder(enriched.value);}// Private helpers last — only read if you need to understand a specific stepfunctionvalidateOrder(input: ProcessOrderInput): Result<ValidatedOrder,ProcessError>{// ...}
// ❌ Implementation soup — must read everything to understand anythingimport{db}from'../globals';constRETRY_COUNT=3;constBACKOFF_MS=100;functionhelper1(){/* ... */}functionhelper2(){/* ... */}// 200 lines later...exportfunctionprocessOrder(){/* ... */}

Documentation Disclosure

Match documentation depth to the reader's likely intent. Most readers want "what does this do?" — very few want "why did you choose bcrypt over argon2?"

Level 1 — CLAUDE.md (5 seconds)
"This is an order processing API. Entry: src/api/server.ts"
Level 2 — Module README (30 seconds) "Orders go through validate → enrich → price → submit. Uses Result types. Retries on transient failures."
Level 3 — Section comments (2 minutes)
// ========================================
// PRICING ENGINE
// ========================================
// Applies tiered discounts, tax rules, and currency conversion.
// See: docs/pricing-model.md for business rules.
Level 4 — Inline "why" comments (as needed)
// Using ceiling division here because partial units // must be billed as full units per the SLA.

API & Type Disclosure

Public interfaces should be scannable summaries. Implementation types stay internal.

// ✅ Public types: minimal, focused, scannable// orders/types.ts — what consumers need to knowexporttypeOrderSummary={id: OrderId;status: OrderStatus;total: Money;itemCount: number;createdAt: DateTime;};// orders/internal-types.ts — implementation detail// Not exported. Contains pricing breakdowns, audit trails,// intermediate computation states, retry metadata, etc.typeOrderPricingContext={/* ... */};typeOrderAuditEntry={/* ... */};

Disclosure Anti-Patterns

  • Premature depth: Putting implementation details in README files
  • Flat disclosure: 500-line files with no visual hierarchy or grouping
  • Inverted disclosure: Helpers at top, public API buried at bottom
  • Missing levels: Jumping from directory listing straight to inline comments with nothing in between

Naming

The #1 impact on readability. Good names eliminate mental translation overhead.

// ✅ Descriptive, unambiguous
async function validateJsonAgainstSchema(
schema: ZodSchema,
input: string
): Promise<ValidationResult>
function calculateExponentialBackoff(
attemptNumber: number,
baseDelayMs: number
): number
// ❌ Vague, abbreviated
async function valJson(s: any, i: string): Promise<any>
function calcBackoff(n: number, d: number): number

Rules:

  1. Be specific: activeUsers not users, httpTimeoutMs not timeout
  2. Include units: delayMs not delay, maxRetries not max
  3. Avoid abbreviations: customer not cust, configuration not cfg
  4. Use domain language: Names from business domain, not technical abstractions
  5. Boolean prefixes: isValid, hasPermission, canEdit, shouldRetry
  6. Verbs for functions: validateEmailFormat() not checkEmail(), fetchActiveUsers() not getUsers()

Function Design

Single Responsibility with Explicit Contracts

// ✅ Self-contained, explicit dependencies, typed contract
async function authenticateUser(
credentials: UserCredentials,
database: Database,
currentTime: DateTime
): Promise<Result<AuthSession, AuthError>> {
// All dependencies visible in signature
// Return type reveals all possible outcomes
}
// ❌ Hidden dependencies, unclear contract
async function auth(data: any): Promise<any> {
// Uses global config, modifies global state
}

Guard Clauses Over Nesting

Handle edge cases first, keep the happy path unindented and visible.

// ✅ Guard clauses — happy path clear
function processOrder(order: Order): Result<Receipt, ProcessError> {
if (!order) return err('missing_order');
if (order.items.length === 0) return err('empty_order');
if (order.total <= 0) return err('invalid_total');
if (!order.paymentMethod) return err('missing_payment');
return ok(completePayment(order));
}
// ❌ Nested conditions — happy path buried
function processOrder(order: Order) {
if (order) {
if (order.items.length > 0) {
if (order.total > 0) {
// Happy path buried 4 levels deep
}
}
}
}

Design Rules

  1. Single responsibility — describable in one sentence
  2. Explicit dependencies — all inputs as parameters, no hidden global state
  3. Type everything — TypeScript strict mode, Python type hints
  4. Self-contained context units — comprehensible without reading other files
  5. 50-line guideline — not a hard limit, but a refactoring trigger

Error Handling

Result Types — Make Errors Explicit

Errors belong in function signatures, not hidden behind throw.

type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
type UserError = 'not_found' | 'unauthorized' | 'network_failure';
async function fetchUser(id: string): Promise<Result<User, UserError>> {
// Errors are part of the contract
}
// Usage forces error handling — compiler catches missing cases
const result = await fetchUser(userId);
if (!result.ok) {
switch (result.error) {
case 'not_found': return show404();
case 'unauthorized': return redirectLogin();
case 'network_failure': return showRetry();
}
}

When to use Result types: API calls, file I/O, validation, any complex error path. When to use exceptions: Truly exceptional/unrecoverable situations (out of memory, corrupted state).

Branded Types — Validate at Boundaries

type ValidatedEmail = string & { readonly __brand: 'ValidatedEmail' };
type UserId = string & { readonly __brand: 'UserId' };
function validateEmail(input: string): ValidatedEmail | null {
return isValidEmail(input) ? (input as ValidatedEmail) : null;
}
// Type system prevents using unvalidated data
function sendEmail(to: ValidatedEmail, subject: string) {
// No need to re-validate — type guarantees validity
}

Once you have a ValidatedEmail, downstream functions carry zero validation overhead. The type system encodes the knowledge that validation occurred.

Error Principles

  1. Never silently swallow errors — log or propagate, never ignore
  2. Fail fast at boundaries — validate inputs immediately, not deep in call stack
  3. Provide actionable messages — what failed, expected vs actual, how to fix
// ✅ Actionable error with context
throw new ValidationError(
`Email validation failed for "user_email": ` +
`Expected "name@domain.com", received "${input}". ` +
`Use validateEmailFormat() to check before calling.`
);
// ❌ Opaque
throw new Error("Validation failed");

File & Module Organization

Structure with Clear Boundaries

// ========================================
// PUBLIC API
// ========================================
export class UserService {
constructor(private readonly db: Database) {}
async createUser(data: CreateUserData): Promise<Result<User, CreateError>> {
// Public interface
}
}
// ========================================
// VALIDATION
// ========================================
function validateUserData(data: unknown): Result<ValidatedData, ValidationError> {
// Grouped validation logic
}
// ========================================
// PRIVATE HELPERS
// ========================================
function hashPassword(password: string): Promise<HashedPassword> {
// Internal implementation
}

Organization Rules

  1. Group by feature/domain, not file type — authentication/, orders/, payments/
  2. Public API first — exported functions at top, helpers at bottom
  3. One major export per fileUserService.ts exports UserService
  4. Co-locate testsUserService.test.ts next to UserService.ts
  5. 300-line guideline — not a hard limit, but a refactoring trigger
  6. Minimal cross-module dependencies — each module is a clean context boundary
project/
├── authentication/ # Self-contained context
│ ├── index.ts # Public API only
│ ├── credentials.ts
│ ├── sessions.ts
│ └── README.md # Module architecture
├── orders/ # Independent context
└── storage/ # Independent context

Testing

Testing Trophy — Mostly Integration

"Write tests. Not too many. Mostly integration." — Kent C. Dodds

  1. Static Analysis (foundation): TypeScript strict mode, ESLint
  2. Unit Tests (narrow): Pure functions, complex algorithms
  3. Integration Tests (widest — most tests here): How pieces work together, where bugs actually live
  4. E2E Tests (top): Critical user journeys only

Tests as Documentation

Test names describe scenarios. Docstrings explain "why." Tests demonstrate usage.

test('should reject invalid credentials without revealing if username exists', async () => {
// Prevents username enumeration attacks
const auth = new Authenticator(database);
const result = await auth.authenticate({
email: 'nonexistent@example.com',
password: 'any-password'
});
expect(result.ok).toBe(false);
expect(result.error.code).toBe('INVALID_CREDENTIALS');
expect(result.error.message).not.toContain('user not found');
});

Testing Rules

  1. Test behavior, not implementation — focus on inputs/outputs, not internal state
  2. One concept per test — don't test multiple unrelated things
  3. Integration over unit — test pieces working together (more confidence per test, more resilient to refactoring)
  4. Clear test names — describe the scenario: test('user can add items to cart')
  5. 80% coverage minimum — focus on critical paths

Observability

Structured Logging

// ✅ Structured — queryable, correlated
logger.info('Request processed', {
request_id: requestId,
user_id: userId,
endpoint: req.path,
method: req.method,
duration_ms: duration,
status_code: res.statusCode,
cache_hit: cacheHit
});
// ❌ Unstructured — hard to query
logger.info(`User ${userId} accessed ${req.path}`);

What to Log

Always include: request_id, user_id, trace_id, entity IDs, operation type, duration_ms, error details.

Log at critical boundaries:

  • External API calls (request/response)
  • Database operations (query, duration)
  • Authentication/authorization decisions
  • Error occurrences with full context

One structured event per operation — derive metrics, logs, or traces from the same data. Don't instrument separately for each observability pillar.

Agentic Coding Patterns

These patterns address the unique demands of code that will be read, modified, and executed by AI agents alongside humans.

Idempotent Operations

Agents retry. Network calls fail. Tasks get re-run. Design every mutation to be safely repeatable.

// ✅ Idempotent — safe to retry
async function ensureUserExists(
email: ValidatedEmail,
db: Database
): Promise<User> {
const existing = await db.users.findByEmail(email);
if (existing) return existing;
return db.users.create({ email });
}
// ❌ Non-idempotent — duplicates on retry
async function createUser(email: string, db: Database): Promise<User> {
return db.users.create({ email });
}

Explicit State Machines Over Implicit Flows

When operations have distinct phases, model them explicitly. Agents reason about state machines far better than implicit status flags scattered across objects.

type OrderState =
| { status: 'draft'; items: Item[] }
| { status: 'submitted'; items: Item[]; submittedAt: DateTime }
| { status: 'paid'; items: Item[]; submittedAt: DateTime; paymentId: string }
| { status: 'shipped'; items: Item[]; trackingNumber: string };
// Each transition is a pure function with clear preconditions
function submitOrder(order: OrderState & { status: 'draft' }): OrderState & { status: 'submitted' } {
return { ...order, status: 'submitted', submittedAt: DateTime.now() };
}

Machine-Parseable Errors

Agents need structured errors alongside human-readable ones. Return error codes that can be programmatically matched, with messages that explain context.

type AppError = {
code: 'VALIDATION_FAILED' | 'NOT_FOUND' | 'CONFLICT' | 'UPSTREAM_TIMEOUT';
message: string; // Human-readable explanation
field?: string; // Which input caused it
retryable: boolean; // Can the caller retry?
};

Atomic, Independently-Verifiable Changes

Structure work so each change can be validated in isolation. This applies to commits, PRs, and function design. An agent (or reviewer) should be able to verify correctness without understanding the entire system.

// ✅ Each function is independently testable and verifiable
function parseConfig(raw: string): Result<Config, ParseError> { /* ... */ }
function validateConfig(config: Config): Result<ValidConfig, ValidationError[]> { /* ... */ }
function applyConfig(config: ValidConfig, system: System): Result<void, ApplyError> { /* ... */ }
// ❌ Monolithic — must understand everything to verify anything
function loadAndApplyConfig(path: string): void { /* 200 lines */ }

Convention Over Configuration

Reduce the search space for agents (and humans). Consistent patterns mean less context needed per decision.

  • Consistent file naming: UserService.ts, UserService.test.ts, UserService.types.ts
  • Predictable directory structure across features
  • Standard patterns for CRUD operations, API endpoints, error handling
  • If your project has a pattern, follow it. If it doesn't, establish one and document it

Contract-First Design

Define types before implementation. Types are the cheapest, most scannable form of documentation. An agent reading your types understands your system's data flow without reading a single function body.

// Define the contract first
interface OrderService {
create(data: CreateOrderInput): Promise<Result<Order, CreateOrderError>>;
cancel(id: OrderId, reason: CancelReason): Promise<Result<void, CancelError>>;
findByUser(userId: UserId, pagination: Pagination): Promise<PaginatedResult<OrderSummary>>;
}
// Then implement — the types guide everything

Observable Side Effects

Every mutation should produce structured output describing what changed. This enables agents to verify their actions and enables humans to audit.

type MutationResult<T> = {
data: T;
changes: Change[]; // What was modified
warnings: string[]; // Non-fatal issues encountered
};
async function updateUserProfile(
id: UserId,
updates: ProfileUpdates
): Promise<Result<MutationResult<UserProfile>, UpdateError>> {
// Returns both the result AND a description of what changed
}

Context Optimisation & Token Economics

Every token an agent reads is a token it can't use for reasoning. Treat context like memory in an embedded system — budget it, measure it, and refuse to waste it.

The Context Budget

AI agents operate within fixed context windows. Your code, documentation, error messages, and tool outputs all compete for the same finite space. Code that is token-efficient isn't just neat — it directly improves agent reasoning quality.

Context Window (finite)
├── System prompt & instructions ~2-5k tokens (fixed cost)
├── Conversation history ~variable
├── Tool definitions ~1-10k tokens (per tool schema)
├── Retrieved code / docs ~variable ← YOU CONTROL THIS
├── Agent reasoning ~variable ← THIS GETS SQUEEZED
└── Output generation ~variable ← AND SO DOES THIS
The more tokens your code consumes, the less room the agent has to think. Optimise ruthlessly.

Semantic Compression

Collapse granular interfaces into high-level semantic operations. Instead of exposing every low-level action, expose intent-based APIs.

// ❌ 15 granular tools = ~15k tokens of schema// An agent must read and reason about ALL of them
tools: [createFile,readFile,deleteFile,moveFile,copyFile,listDirectory,createDirectory,deleteDirectory,getFileMetadata,setFilePermissions,watchFile,compressFile,decompressFile,hashFile,diffFiles]// ✅ 1 semantic dispatcher = ~1k tokens of schema// Agent reasons about intent, not mechanics
tools: [{name: "filesystem",description: "Manage files and directories",parameters: {operation: "create | read | delete | move | copy | list | ...",path: "string",options: "object (operation-specific)"}}]

This is the dispatcher pattern: consolidate related tools behind a single entry point that routes by intent. Token cost drops dramatically while functionality stays the same.

Layered Context Loading

Don't front-load everything. Provide summaries first, with drill-down paths for when the agent actually needs more detail.

// ✅ Layered: summary first, details on demandfunctiongetProjectOverview(): ProjectSummary{return{name: "DataPipeline",modules: ["ingestion","transform","export"],entryPoint: "src/main.ts",recentChanges: getRecentChangeSummary(5),// Drill-down references — agent only loads what it needsgetModuleDetail: (name: string)=>loadModuleContext(name),getFileContent: (path: string)=>loadFileContext(path),};}// ❌ Eager: dumps everything into context upfrontfunctiongetProjectContext(): FullProjectDump{return{allFiles: readAllFiles(),// 50k tokensallTests: readAllTests(),// 30k tokensallDocs: readAllDocs(),// 20k tokens// Agent's context window is now full before it starts thinking};}

Token-Aware Documentation

Write documentation that serves both human readers and token budgets. Every word should earn its place.

# ❌ Token-heavy: narrative style, repetitive, verbose## Overview of the Authentication Module
The authentication module is responsible for handling all aspects of user authentication within our application. This module was designed with security best practices in mind and implements industry-standard protocols. The module handles user login, token generation, session management, and token refresh functionality. It is important to note that this module uses JWT tokens for authentication purposes.
(~80 tokens to say what could be said in 15)
# ✅ Token-efficient: dense, scannable, no filler## Authentication
JWT-based auth with refresh token rotation.
- Entry: `authenticate()``Result<Session, AuthError>`- Tokens: 15min access, 7d refresh (HTTP-only cookie)
- Storage: PostgreSQL users, Redis token blacklist
(~40 tokens, more information conveyed)

Structured Output for Agent Consumption

When building tools or functions that agents will consume, prefer structured, parseable output over human-readable prose.

// ✅ Agent-friendly: structured, parseable, minimaltypeBuildResult={success: boolean;errors: {file: string;line: number;code: string;message: string}[];warnings: {file: string;line: number;code: string;message: string}[];stats: {duration_ms: number;filesProcessed: number};};// ❌ Human-only: requires parsing natural languagefunctiongetBuildOutput(): string{return`Build completed with 2 errors and 1 warning. Error in src/auth.ts line 42: Type 'string' is not assignable... Error in src/orders.ts line 18: Property 'id' does not exist... Warning in src/utils.ts line 7: Unused variable 'temp'... Build took 3.2 seconds, processed 47 files.`;}

Context Boundaries as Architecture

Design modules so an agent can work within one module without loading others. Each module should be a self-contained context unit.

// ✅ Clean context boundary — agent only needs this module// payments/index.tsexportinterfacePaymentService{charge(input: ChargeInput): Promise<Result<Payment,PaymentError>>;refund(id: PaymentId,reason: RefundReason): Promise<Result<Refund,RefundError>>;}// payments/types.ts — all types co-located, no external dependenciesexporttypeChargeInput={amount: Money;method: PaymentMethod;idempotencyKey: string;// Agent-friendly: built-in retry safety};// payments/errors.ts — exhaustive, machine-readableexporttypePaymentError=|{code: 'INSUFFICIENT_FUNDS';available: Money}|{code: 'CARD_DECLINED';reason: string;retryable: false}|{code: 'GATEWAY_TIMEOUT';retryable: true};
// ❌ Leaky context boundary — agent must load 4 modules to understand 1
// payments/index.ts
import { User } from '../users/types';
import { Order } from '../orders/types';
import { AuditLogger } from '../audit/logger';
import { ConfigManager } from '../config/manager';
// Agent now needs context from users/, orders/, audit/, config/

Compression Strategies Reference

StrategyBeforeAfterSavings
Semantic dispatchersN tool schemas (~N × 1k tokens)1 dispatcher (~1k tokens)~(N-1)k tokens
Layered loadingFull dump (50k tokens)Summary + drill-down (2k + on-demand)~48k idle tokens
Dense docsNarrative prose (~80 tokens/concept)Structured bullets (~40 tokens/concept)~50%
Co-located typesScattered across modulesSingle types.ts per moduleFewer file loads
Summary-first returnsFull object graphsSummary + reference IDs60-90% per call
Discriminated unionsGeneric error + message stringTyped union with code fieldEliminates parsing

Project Navigation

CLAUDE.md at Project Root

Every project needs a navigation file. List entry points, patterns, and common tasks.

# Project: Data Processing Pipeline
## Entry Points
- `src/main.ts`: CLI interface
- `src/api/server.ts`: REST API
- `src/processors/pipeline.ts`: Core processing
## Key Patterns
- All processors implement `Processor` interface (src/processors/base.ts)
- Config uses Zod schemas (src/config/schemas.ts)
- External APIs via `APIClient` (src/external/client.ts)
## Common Tasks
- Add data source → implement `DataSource` in `src/api/sources/`
- Add transformation → implement `Transformer` in `src/processors/transformers/`

Keep under 200 lines. Update when architecture changes.

Module-Level READMEs

Every major directory gets a README answering: What is this? How does it work? What are the gotchas?

# Module: User Authentication
## Purpose
JWT-based authentication with refresh token rotation
## Key Decisions
- bcrypt cost factor 12 for password hashing
- Access tokens expire after 15 minutes
- Refresh tokens stored in HTTP-only cookies
## Dependencies
- jose library for JWT (not jsonwebtoken — more secure)
- PostgreSQL for user storage
- Redis for token blacklist

Progressive Context Hierarchy

  1. CLAUDE.md / README.md at root — system overview, entry points, setup
  2. README.md per major module — module purpose, key decisions, patterns
  3. Section comments in files — group related code with clear headers
  4. Function/class docs — purpose, examples for non-obvious APIs
  5. Inline comments — only for "why" decisions

Anti-Patterns

  • Premature optimization — Measure first, optimize second
  • Hasty abstractions — Wait for 3rd duplication before extracting
  • Clever code — Simple and obvious beats clever and compact
  • Silent failures — Log and propagate, never swallow
  • Vague interfacesprocess(data: any): any provides zero guidance
  • Hidden dependencies — Global state, singletons, ambient imports
  • Nested conditionals — Use guard clauses instead
  • Comments describing "what" — If you need a comment to explain what code does, rename things
  • Premature generalization — Build for today's requirements, not hypothetical futures
  • Token bloat — Functions returning everything when callers need summaries
  • Inverted disclosure — Helpers at top, public API buried at bottom
  • Flat files — 500-line files with no visual hierarchy, grouping, or section comments
  • Leaky context boundaries — Modules that import heavily from siblings, forcing agents to load the entire codebase
  • Eager context loading — Dumping full project state into agent context when a summary would suffice

Checklist

Before submitting code:

  • Solves the stated problem with minimal code?
  • A new developer can understand it without extensive context?
  • Errors handled with actionable messages?
  • Names clear, specific, and unambiguous?
  • Functions have single, clear responsibilities?
  • Dependencies explicit (no hidden global state)?
  • Tests cover critical paths?
  • Operations idempotent where applicable?
  • Types define contracts before implementation?
  • Would this work well with ~200 lines of surrounding context?
  • Can an agent understand this module without loading adjacent modules?
  • Are public APIs scannable in under 50 lines?
  • Do tool/function outputs use structured types, not prose?
  • Is documentation token-dense (no filler words, no repetition)?
  • Does the file follow progressive disclosure (types → public API → helpers)?

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

About

A quick reference to the living codestyle guidelines I feed to my AI agents, to help maintain scalable repositories.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

(See CLAUDE.md for sharper context-saving alternative)

Code Style Guide

Core Principle: Context is finite. Every token — code, comment, structure — competes for limited attention. Maximize signal, minimize noise. Write for two audiences: humans with limited working memory and AI agents with bounded context windows.

Philosophy

The optimal code is the minimum necessary to solve the problem correctly. Every additional line is debt.

Progressive Disclosure: Structure code layer-by-layer. Readers grasp high-level flow immediately, drilling into details only when needed. File names indicate purpose. Directory structures mirror conceptual hierarchies. Function names describe behavior without reading implementation. See Progressive Disclosure for concrete patterns.

Self-Documenting: Names eliminate need for comments. Comments explain "why," never "what." If you chose algorithm A over B for subtle reasons, state that. If you're working around a library bug, explain it.

Aggressive Minimalism: Before adding code, ask: "Is this the simplest solution?" Before adding a comment: "Does this clarify something non-obvious?" Before introducing an abstraction: "Does this reduce complexity, or merely relocate it?"

AHA Over DRY: Avoid Hasty Abstractions. Wait for the 3rd duplication before extracting. The wrong abstraction is worse than duplication. Three similar lines of code is better than a premature abstraction.

Progressive Disclosure

Structure every layer of your system so readers — human or agent — get the right level of detail at the right time. No one should need to read 2000 lines to understand what a module does.

The Zoom Principle

Code should work like a map: zoom out for the big picture, zoom in for street-level detail. Each zoom level should be self-sufficient.

// Level 0: Directory structure tells you what exists
src/
├── authentication/ # "There's an auth system"
├── orders/ # "There's an order system" ├── payments/ # "There's a payment system"
└── README.md # How they connect
// Level 1: Index file tells you what it can do
// authentication/index.ts
export { authenticateUser } from './authenticate';
export { refreshSession } from './sessions';
export { revokeAccess } from './revoke';
// No implementation visible — just capabilities
// Level 2: Function signature tells you the contract
async function authenticateUser(
credentials: UserCredentials,
db: Database,
clock: Clock
): Promise<Result<AuthSession, AuthError>>
// Level 3: Implementation tells you how
// Only read this when you need to change the behaviour

File-Level Disclosure

Every file should answer "what is this?" in its first 10 lines. Implementation details belong below.

// ✅ Top of file reveals purpose, contract, and shape/** * Order Processing Pipeline *  * Validates → enriches → prices → submits orders. * Entry point: processOrder() * Error strategy: Result types, no throws */// Types first — the contracttypeProcessOrderInput={/* ... */};typeProcessOrderResult=Result<Receipt,ProcessError>;// Public API secondexportasyncfunctionprocessOrder(input: ProcessOrderInput): Promise<ProcessOrderResult>{constvalidated=validateOrder(input);if(!validated.ok)returnvalidated;constenriched=awaitenrichWithInventory(validated.value);if(!enriched.ok)returnenriched;returnsubmitOrder(enriched.value);}// Private helpers last — only read if you need to understand a specific stepfunctionvalidateOrder(input: ProcessOrderInput): Result<ValidatedOrder,ProcessError>{// ...}
// ❌ Implementation soup — must read everything to understand anythingimport{db}from'../globals';constRETRY_COUNT=3;constBACKOFF_MS=100;functionhelper1(){/* ... */}functionhelper2(){/* ... */}// 200 lines later...exportfunctionprocessOrder(){/* ... */}

Documentation Disclosure

Match documentation depth to the reader's likely intent. Most readers want "what does this do?" — very few want "why did you choose bcrypt over argon2?"

Level 1 — CLAUDE.md (5 seconds)
"This is an order processing API. Entry: src/api/server.ts"
Level 2 — Module README (30 seconds) "Orders go through validate → enrich → price → submit. Uses Result types. Retries on transient failures."
Level 3 — Section comments (2 minutes)
// ========================================
// PRICING ENGINE
// ========================================
// Applies tiered discounts, tax rules, and currency conversion.
// See: docs/pricing-model.md for business rules.
Level 4 — Inline "why" comments (as needed)
// Using ceiling division here because partial units // must be billed as full units per the SLA.

API & Type Disclosure

Public interfaces should be scannable summaries. Implementation types stay internal.

// ✅ Public types: minimal, focused, scannable// orders/types.ts — what consumers need to knowexporttypeOrderSummary={id: OrderId;status: OrderStatus;total: Money;itemCount: number;createdAt: DateTime;};// orders/internal-types.ts — implementation detail// Not exported. Contains pricing breakdowns, audit trails,// intermediate computation states, retry metadata, etc.typeOrderPricingContext={/* ... */};typeOrderAuditEntry={/* ... */};

Disclosure Anti-Patterns

  • Premature depth: Putting implementation details in README files
  • Flat disclosure: 500-line files with no visual hierarchy or grouping
  • Inverted disclosure: Helpers at top, public API buried at bottom
  • Missing levels: Jumping from directory listing straight to inline comments with nothing in between

Naming

The #1 impact on readability. Good names eliminate mental translation overhead.

// ✅ Descriptive, unambiguous
async function validateJsonAgainstSchema(
schema: ZodSchema,
input: string
): Promise<ValidationResult>
function calculateExponentialBackoff(
attemptNumber: number,
baseDelayMs: number
): number
// ❌ Vague, abbreviated
async function valJson(s: any, i: string): Promise<any>
function calcBackoff(n: number, d: number): number

Rules:

  1. Be specific: activeUsers not users, httpTimeoutMs not timeout
  2. Include units: delayMs not delay, maxRetries not max
  3. Avoid abbreviations: customer not cust, configuration not cfg
  4. Use domain language: Names from business domain, not technical abstractions
  5. Boolean prefixes: isValid, hasPermission, canEdit, shouldRetry
  6. Verbs for functions: validateEmailFormat() not checkEmail(), fetchActiveUsers() not getUsers()

Function Design

Single Responsibility with Explicit Contracts

// ✅ Self-contained, explicit dependencies, typed contract
async function authenticateUser(
credentials: UserCredentials,
database: Database,
currentTime: DateTime
): Promise<Result<AuthSession, AuthError>> {
// All dependencies visible in signature
// Return type reveals all possible outcomes
}
// ❌ Hidden dependencies, unclear contract
async function auth(data: any): Promise<any> {
// Uses global config, modifies global state
}

Guard Clauses Over Nesting

Handle edge cases first, keep the happy path unindented and visible.

// ✅ Guard clauses — happy path clear
function processOrder(order: Order): Result<Receipt, ProcessError> {
if (!order) return err('missing_order');
if (order.items.length === 0) return err('empty_order');
if (order.total <= 0) return err('invalid_total');
if (!order.paymentMethod) return err('missing_payment');
return ok(completePayment(order));
}
// ❌ Nested conditions — happy path buried
function processOrder(order: Order) {
if (order) {
if (order.items.length > 0) {
if (order.total > 0) {
// Happy path buried 4 levels deep
}
}
}
}

Design Rules

  1. Single responsibility — describable in one sentence
  2. Explicit dependencies — all inputs as parameters, no hidden global state
  3. Type everything — TypeScript strict mode, Python type hints
  4. Self-contained context units — comprehensible without reading other files
  5. 50-line guideline — not a hard limit, but a refactoring trigger

Error Handling

Result Types — Make Errors Explicit

Errors belong in function signatures, not hidden behind throw.

type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
type UserError = 'not_found' | 'unauthorized' | 'network_failure';
async function fetchUser(id: string): Promise<Result<User, UserError>> {
// Errors are part of the contract
}
// Usage forces error handling — compiler catches missing cases
const result = await fetchUser(userId);
if (!result.ok) {
switch (result.error) {
case 'not_found': return show404();
case 'unauthorized': return redirectLogin();
case 'network_failure': return showRetry();
}
}

When to use Result types: API calls, file I/O, validation, any complex error path. When to use exceptions: Truly exceptional/unrecoverable situations (out of memory, corrupted state).

Branded Types — Validate at Boundaries

type ValidatedEmail = string & { readonly __brand: 'ValidatedEmail' };
type UserId = string & { readonly __brand: 'UserId' };
function validateEmail(input: string): ValidatedEmail | null {
return isValidEmail(input) ? (input as ValidatedEmail) : null;
}
// Type system prevents using unvalidated data
function sendEmail(to: ValidatedEmail, subject: string) {
// No need to re-validate — type guarantees validity
}

Once you have a ValidatedEmail, downstream functions carry zero validation overhead. The type system encodes the knowledge that validation occurred.

Error Principles

  1. Never silently swallow errors — log or propagate, never ignore
  2. Fail fast at boundaries — validate inputs immediately, not deep in call stack
  3. Provide actionable messages — what failed, expected vs actual, how to fix
// ✅ Actionable error with context
throw new ValidationError(
`Email validation failed for "user_email": ` +
`Expected "name@domain.com", received "${input}". ` +
`Use validateEmailFormat() to check before calling.`
);
// ❌ Opaque
throw new Error("Validation failed");

File & Module Organization

Structure with Clear Boundaries

// ========================================
// PUBLIC API
// ========================================
export class UserService {
constructor(private readonly db: Database) {}
async createUser(data: CreateUserData): Promise<Result<User, CreateError>> {
// Public interface
}
}
// ========================================
// VALIDATION
// ========================================
function validateUserData(data: unknown): Result<ValidatedData, ValidationError> {
// Grouped validation logic
}
// ========================================
// PRIVATE HELPERS
// ========================================
function hashPassword(password: string): Promise<HashedPassword> {
// Internal implementation
}

Organization Rules

  1. Group by feature/domain, not file type — authentication/, orders/, payments/
  2. Public API first — exported functions at top, helpers at bottom
  3. One major export per fileUserService.ts exports UserService
  4. Co-locate testsUserService.test.ts next to UserService.ts
  5. 300-line guideline — not a hard limit, but a refactoring trigger
  6. Minimal cross-module dependencies — each module is a clean context boundary
project/
├── authentication/ # Self-contained context
│ ├── index.ts # Public API only
│ ├── credentials.ts
│ ├── sessions.ts
│ └── README.md # Module architecture
├── orders/ # Independent context
└── storage/ # Independent context

Testing

Testing Trophy — Mostly Integration

"Write tests. Not too many. Mostly integration." — Kent C. Dodds

  1. Static Analysis (foundation): TypeScript strict mode, ESLint
  2. Unit Tests (narrow): Pure functions, complex algorithms
  3. Integration Tests (widest — most tests here): How pieces work together, where bugs actually live
  4. E2E Tests (top): Critical user journeys only

Tests as Documentation

Test names describe scenarios. Docstrings explain "why." Tests demonstrate usage.

test('should reject invalid credentials without revealing if username exists', async () => {
// Prevents username enumeration attacks
const auth = new Authenticator(database);
const result = await auth.authenticate({
email: 'nonexistent@example.com',
password: 'any-password'
});
expect(result.ok).toBe(false);
expect(result.error.code).toBe('INVALID_CREDENTIALS');
expect(result.error.message).not.toContain('user not found');
});

Testing Rules

  1. Test behavior, not implementation — focus on inputs/outputs, not internal state
  2. One concept per test — don't test multiple unrelated things
  3. Integration over unit — test pieces working together (more confidence per test, more resilient to refactoring)
  4. Clear test names — describe the scenario: test('user can add items to cart')
  5. 80% coverage minimum — focus on critical paths

Observability

Structured Logging

// ✅ Structured — queryable, correlated
logger.info('Request processed', {
request_id: requestId,
user_id: userId,
endpoint: req.path,
method: req.method,
duration_ms: duration,
status_code: res.statusCode,
cache_hit: cacheHit
});
// ❌ Unstructured — hard to query
logger.info(`User ${userId} accessed ${req.path}`);

What to Log

Always include: request_id, user_id, trace_id, entity IDs, operation type, duration_ms, error details.

Log at critical boundaries:

  • External API calls (request/response)
  • Database operations (query, duration)
  • Authentication/authorization decisions
  • Error occurrences with full context

One structured event per operation — derive metrics, logs, or traces from the same data. Don't instrument separately for each observability pillar.

Agentic Coding Patterns

These patterns address the unique demands of code that will be read, modified, and executed by AI agents alongside humans.

Idempotent Operations

Agents retry. Network calls fail. Tasks get re-run. Design every mutation to be safely repeatable.

// ✅ Idempotent — safe to retry
async function ensureUserExists(
email: ValidatedEmail,
db: Database
): Promise<User> {
const existing = await db.users.findByEmail(email);
if (existing) return existing;
return db.users.create({ email });
}
// ❌ Non-idempotent — duplicates on retry
async function createUser(email: string, db: Database): Promise<User> {
return db.users.create({ email });
}

Explicit State Machines Over Implicit Flows

When operations have distinct phases, model them explicitly. Agents reason about state machines far better than implicit status flags scattered across objects.

type OrderState =
| { status: 'draft'; items: Item[] }
| { status: 'submitted'; items: Item[]; submittedAt: DateTime }
| { status: 'paid'; items: Item[]; submittedAt: DateTime; paymentId: string }
| { status: 'shipped'; items: Item[]; trackingNumber: string };
// Each transition is a pure function with clear preconditions
function submitOrder(order: OrderState & { status: 'draft' }): OrderState & { status: 'submitted' } {
return { ...order, status: 'submitted', submittedAt: DateTime.now() };
}

Machine-Parseable Errors

Agents need structured errors alongside human-readable ones. Return error codes that can be programmatically matched, with messages that explain context.

type AppError = {
code: 'VALIDATION_FAILED' | 'NOT_FOUND' | 'CONFLICT' | 'UPSTREAM_TIMEOUT';
message: string; // Human-readable explanation
field?: string; // Which input caused it
retryable: boolean; // Can the caller retry?
};

Atomic, Independently-Verifiable Changes

Structure work so each change can be validated in isolation. This applies to commits, PRs, and function design. An agent (or reviewer) should be able to verify correctness without understanding the entire system.

// ✅ Each function is independently testable and verifiable
function parseConfig(raw: string): Result<Config, ParseError> { /* ... */ }
function validateConfig(config: Config): Result<ValidConfig, ValidationError[]> { /* ... */ }
function applyConfig(config: ValidConfig, system: System): Result<void, ApplyError> { /* ... */ }
// ❌ Monolithic — must understand everything to verify anything
function loadAndApplyConfig(path: string): void { /* 200 lines */ }

Convention Over Configuration

Reduce the search space for agents (and humans). Consistent patterns mean less context needed per decision.

  • Consistent file naming: UserService.ts, UserService.test.ts, UserService.types.ts
  • Predictable directory structure across features
  • Standard patterns for CRUD operations, API endpoints, error handling
  • If your project has a pattern, follow it. If it doesn't, establish one and document it

Contract-First Design

Define types before implementation. Types are the cheapest, most scannable form of documentation. An agent reading your types understands your system's data flow without reading a single function body.

// Define the contract first
interface OrderService {
create(data: CreateOrderInput): Promise<Result<Order, CreateOrderError>>;
cancel(id: OrderId, reason: CancelReason): Promise<Result<void, CancelError>>;
findByUser(userId: UserId, pagination: Pagination): Promise<PaginatedResult<OrderSummary>>;
}
// Then implement — the types guide everything

Observable Side Effects

Every mutation should produce structured output describing what changed. This enables agents to verify their actions and enables humans to audit.

type MutationResult<T> = {
data: T;
changes: Change[]; // What was modified
warnings: string[]; // Non-fatal issues encountered
};
async function updateUserProfile(
id: UserId,
updates: ProfileUpdates
): Promise<Result<MutationResult<UserProfile>, UpdateError>> {
// Returns both the result AND a description of what changed
}

Context Optimisation & Token Economics

Every token an agent reads is a token it can't use for reasoning. Treat context like memory in an embedded system — budget it, measure it, and refuse to waste it.

The Context Budget

AI agents operate within fixed context windows. Your code, documentation, error messages, and tool outputs all compete for the same finite space. Code that is token-efficient isn't just neat — it directly improves agent reasoning quality.

Context Window (finite)
├── System prompt & instructions ~2-5k tokens (fixed cost)
├── Conversation history ~variable
├── Tool definitions ~1-10k tokens (per tool schema)
├── Retrieved code / docs ~variable ← YOU CONTROL THIS
├── Agent reasoning ~variable ← THIS GETS SQUEEZED
└── Output generation ~variable ← AND SO DOES THIS
The more tokens your code consumes, the less room the agent has to think. Optimise ruthlessly.

Semantic Compression

Collapse granular interfaces into high-level semantic operations. Instead of exposing every low-level action, expose intent-based APIs.

// ❌ 15 granular tools = ~15k tokens of schema// An agent must read and reason about ALL of them
tools: [createFile,readFile,deleteFile,moveFile,copyFile,listDirectory,createDirectory,deleteDirectory,getFileMetadata,setFilePermissions,watchFile,compressFile,decompressFile,hashFile,diffFiles]// ✅ 1 semantic dispatcher = ~1k tokens of schema// Agent reasons about intent, not mechanics
tools: [{name: "filesystem",description: "Manage files and directories",parameters: {operation: "create | read | delete | move | copy | list | ...",path: "string",options: "object (operation-specific)"}}]

This is the dispatcher pattern: consolidate related tools behind a single entry point that routes by intent. Token cost drops dramatically while functionality stays the same.

Layered Context Loading

Don't front-load everything. Provide summaries first, with drill-down paths for when the agent actually needs more detail.

// ✅ Layered: summary first, details on demandfunctiongetProjectOverview(): ProjectSummary{return{name: "DataPipeline",modules: ["ingestion","transform","export"],entryPoint: "src/main.ts",recentChanges: getRecentChangeSummary(5),// Drill-down references — agent only loads what it needsgetModuleDetail: (name: string)=>loadModuleContext(name),getFileContent: (path: string)=>loadFileContext(path),};}// ❌ Eager: dumps everything into context upfrontfunctiongetProjectContext(): FullProjectDump{return{allFiles: readAllFiles(),// 50k tokensallTests: readAllTests(),// 30k tokensallDocs: readAllDocs(),// 20k tokens// Agent's context window is now full before it starts thinking};}

Token-Aware Documentation

Write documentation that serves both human readers and token budgets. Every word should earn its place.

# ❌ Token-heavy: narrative style, repetitive, verbose## Overview of the Authentication Module
The authentication module is responsible for handling all aspects of user authentication within our application. This module was designed with security best practices in mind and implements industry-standard protocols. The module handles user login, token generation, session management, and token refresh functionality. It is important to note that this module uses JWT tokens for authentication purposes.
(~80 tokens to say what could be said in 15)
# ✅ Token-efficient: dense, scannable, no filler## Authentication
JWT-based auth with refresh token rotation.
- Entry: `authenticate()``Result<Session, AuthError>`- Tokens: 15min access, 7d refresh (HTTP-only cookie)
- Storage: PostgreSQL users, Redis token blacklist
(~40 tokens, more information conveyed)

Structured Output for Agent Consumption

When building tools or functions that agents will consume, prefer structured, parseable output over human-readable prose.

// ✅ Agent-friendly: structured, parseable, minimaltypeBuildResult={success: boolean;errors: {file: string;line: number;code: string;message: string}[];warnings: {file: string;line: number;code: string;message: string}[];stats: {duration_ms: number;filesProcessed: number};};// ❌ Human-only: requires parsing natural languagefunctiongetBuildOutput(): string{return`Build completed with 2 errors and 1 warning. Error in src/auth.ts line 42: Type 'string' is not assignable... Error in src/orders.ts line 18: Property 'id' does not exist... Warning in src/utils.ts line 7: Unused variable 'temp'... Build took 3.2 seconds, processed 47 files.`;}

Context Boundaries as Architecture

Design modules so an agent can work within one module without loading others. Each module should be a self-contained context unit.

// ✅ Clean context boundary — agent only needs this module// payments/index.tsexportinterfacePaymentService{charge(input: ChargeInput): Promise<Result<Payment,PaymentError>>;refund(id: PaymentId,reason: RefundReason): Promise<Result<Refund,RefundError>>;}// payments/types.ts — all types co-located, no external dependenciesexporttypeChargeInput={amount: Money;method: PaymentMethod;idempotencyKey: string;// Agent-friendly: built-in retry safety};// payments/errors.ts — exhaustive, machine-readableexporttypePaymentError=|{code: 'INSUFFICIENT_FUNDS';available: Money}|{code: 'CARD_DECLINED';reason: string;retryable: false}|{code: 'GATEWAY_TIMEOUT';retryable: true};
// ❌ Leaky context boundary — agent must load 4 modules to understand 1
// payments/index.ts
import { User } from '../users/types';
import { Order } from '../orders/types';
import { AuditLogger } from '../audit/logger';
import { ConfigManager } from '../config/manager';
// Agent now needs context from users/, orders/, audit/, config/

Compression Strategies Reference

StrategyBeforeAfterSavings
Semantic dispatchersN tool schemas (~N × 1k tokens)1 dispatcher (~1k tokens)~(N-1)k tokens
Layered loadingFull dump (50k tokens)Summary + drill-down (2k + on-demand)~48k idle tokens
Dense docsNarrative prose (~80 tokens/concept)Structured bullets (~40 tokens/concept)~50%
Co-located typesScattered across modulesSingle types.ts per moduleFewer file loads
Summary-first returnsFull object graphsSummary + reference IDs60-90% per call
Discriminated unionsGeneric error + message stringTyped union with code fieldEliminates parsing

Project Navigation

CLAUDE.md at Project Root

Every project needs a navigation file. List entry points, patterns, and common tasks.

# Project: Data Processing Pipeline
## Entry Points
- `src/main.ts`: CLI interface
- `src/api/server.ts`: REST API
- `src/processors/pipeline.ts`: Core processing
## Key Patterns
- All processors implement `Processor` interface (src/processors/base.ts)
- Config uses Zod schemas (src/config/schemas.ts)
- External APIs via `APIClient` (src/external/client.ts)
## Common Tasks
- Add data source → implement `DataSource` in `src/api/sources/`
- Add transformation → implement `Transformer` in `src/processors/transformers/`

Keep under 200 lines. Update when architecture changes.

Module-Level READMEs

Every major directory gets a README answering: What is this? How does it work? What are the gotchas?

# Module: User Authentication
## Purpose
JWT-based authentication with refresh token rotation
## Key Decisions
- bcrypt cost factor 12 for password hashing
- Access tokens expire after 15 minutes
- Refresh tokens stored in HTTP-only cookies
## Dependencies
- jose library for JWT (not jsonwebtoken — more secure)
- PostgreSQL for user storage
- Redis for token blacklist

Progressive Context Hierarchy

  1. CLAUDE.md / README.md at root — system overview, entry points, setup
  2. README.md per major module — module purpose, key decisions, patterns
  3. Section comments in files — group related code with clear headers
  4. Function/class docs — purpose, examples for non-obvious APIs
  5. Inline comments — only for "why" decisions

Anti-Patterns

  • Premature optimization — Measure first, optimize second
  • Hasty abstractions — Wait for 3rd duplication before extracting
  • Clever code — Simple and obvious beats clever and compact
  • Silent failures — Log and propagate, never swallow
  • Vague interfacesprocess(data: any): any provides zero guidance
  • Hidden dependencies — Global state, singletons, ambient imports
  • Nested conditionals — Use guard clauses instead
  • Comments describing "what" — If you need a comment to explain what code does, rename things
  • Premature generalization — Build for today's requirements, not hypothetical futures
  • Token bloat — Functions returning everything when callers need summaries
  • Inverted disclosure — Helpers at top, public API buried at bottom
  • Flat files — 500-line files with no visual hierarchy, grouping, or section comments
  • Leaky context boundaries — Modules that import heavily from siblings, forcing agents to load the entire codebase
  • Eager context loading — Dumping full project state into agent context when a summary would suffice

Checklist

Before submitting code:

  • Solves the stated problem with minimal code?
  • A new developer can understand it without extensive context?
  • Errors handled with actionable messages?
  • Names clear, specific, and unambiguous?
  • Functions have single, clear responsibilities?
  • Dependencies explicit (no hidden global state)?
  • Tests cover critical paths?
  • Operations idempotent where applicable?
  • Types define contracts before implementation?
  • Would this work well with ~200 lines of surrounding context?
  • Can an agent understand this module without loading adjacent modules?
  • Are public APIs scannable in under 50 lines?
  • Do tool/function outputs use structured types, not prose?
  • Is documentation token-dense (no filler words, no repetition)?
  • Does the file follow progressive disclosure (types → public API → helpers)?

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

About

A quick reference to the living codestyle guidelines I feed to my AI agents, to help maintain scalable repositories.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

(See CLAUDE.md for sharper context-saving alternative)

Code Style Guide

Core Principle: Context is finite. Every token — code, comment, structure — competes for limited attention. Maximize signal, minimize noise. Write for two audiences: humans with limited working memory and AI agents with bounded context windows.

Philosophy

The optimal code is the minimum necessary to solve the problem correctly. Every additional line is debt.

Progressive Disclosure: Structure code layer-by-layer. Readers grasp high-level flow immediately, drilling into details only when needed. File names indicate purpose. Directory structures mirror conceptual hierarchies. Function names describe behavior without reading implementation. See Progressive Disclosure for concrete patterns.

Self-Documenting: Names eliminate need for comments. Comments explain "why," never "what." If you chose algorithm A over B for subtle reasons, state that. If you're working around a library bug, explain it.

Aggressive Minimalism: Before adding code, ask: "Is this the simplest solution?" Before adding a comment: "Does this clarify something non-obvious?" Before introducing an abstraction: "Does this reduce complexity, or merely relocate it?"

AHA Over DRY: Avoid Hasty Abstractions. Wait for the 3rd duplication before extracting. The wrong abstraction is worse than duplication. Three similar lines of code is better than a premature abstraction.

Progressive Disclosure

Structure every layer of your system so readers — human or agent — get the right level of detail at the right time. No one should need to read 2000 lines to understand what a module does.

The Zoom Principle

Code should work like a map: zoom out for the big picture, zoom in for street-level detail. Each zoom level should be self-sufficient.

// Level 0: Directory structure tells you what exists
src/
├── authentication/ # "There's an auth system"
├── orders/ # "There's an order system" ├── payments/ # "There's a payment system"
└── README.md # How they connect
// Level 1: Index file tells you what it can do
// authentication/index.ts
export { authenticateUser } from './authenticate';
export { refreshSession } from './sessions';
export { revokeAccess } from './revoke';
// No implementation visible — just capabilities
// Level 2: Function signature tells you the contract
async function authenticateUser(
credentials: UserCredentials,
db: Database,
clock: Clock
): Promise<Result<AuthSession, AuthError>>
// Level 3: Implementation tells you how
// Only read this when you need to change the behaviour

File-Level Disclosure

Every file should answer "what is this?" in its first 10 lines. Implementation details belong below.

// ✅ Top of file reveals purpose, contract, and shape/** * Order Processing Pipeline *  * Validates → enriches → prices → submits orders. * Entry point: processOrder() * Error strategy: Result types, no throws */// Types first — the contracttypeProcessOrderInput={/* ... */};typeProcessOrderResult=Result<Receipt,ProcessError>;// Public API secondexportasyncfunctionprocessOrder(input: ProcessOrderInput): Promise<ProcessOrderResult>{constvalidated=validateOrder(input);if(!validated.ok)returnvalidated;constenriched=awaitenrichWithInventory(validated.value);if(!enriched.ok)returnenriched;returnsubmitOrder(enriched.value);}// Private helpers last — only read if you need to understand a specific stepfunctionvalidateOrder(input: ProcessOrderInput): Result<ValidatedOrder,ProcessError>{// ...}
// ❌ Implementation soup — must read everything to understand anythingimport{db}from'../globals';constRETRY_COUNT=3;constBACKOFF_MS=100;functionhelper1(){/* ... */}functionhelper2(){/* ... */}// 200 lines later...exportfunctionprocessOrder(){/* ... */}

Documentation Disclosure

Match documentation depth to the reader's likely intent. Most readers want "what does this do?" — very few want "why did you choose bcrypt over argon2?"

Level 1 — CLAUDE.md (5 seconds)
"This is an order processing API. Entry: src/api/server.ts"
Level 2 — Module README (30 seconds) "Orders go through validate → enrich → price → submit. Uses Result types. Retries on transient failures."
Level 3 — Section comments (2 minutes)
// ========================================
// PRICING ENGINE
// ========================================
// Applies tiered discounts, tax rules, and currency conversion.
// See: docs/pricing-model.md for business rules.
Level 4 — Inline "why" comments (as needed)
// Using ceiling division here because partial units // must be billed as full units per the SLA.

API & Type Disclosure

Public interfaces should be scannable summaries. Implementation types stay internal.

// ✅ Public types: minimal, focused, scannable// orders/types.ts — what consumers need to knowexporttypeOrderSummary={id: OrderId;status: OrderStatus;total: Money;itemCount: number;createdAt: DateTime;};// orders/internal-types.ts — implementation detail// Not exported. Contains pricing breakdowns, audit trails,// intermediate computation states, retry metadata, etc.typeOrderPricingContext={/* ... */};typeOrderAuditEntry={/* ... */};

Disclosure Anti-Patterns

  • Premature depth: Putting implementation details in README files
  • Flat disclosure: 500-line files with no visual hierarchy or grouping
  • Inverted disclosure: Helpers at top, public API buried at bottom
  • Missing levels: Jumping from directory listing straight to inline comments with nothing in between

Naming

The #1 impact on readability. Good names eliminate mental translation overhead.

// ✅ Descriptive, unambiguous
async function validateJsonAgainstSchema(
schema: ZodSchema,
input: string
): Promise<ValidationResult>
function calculateExponentialBackoff(
attemptNumber: number,
baseDelayMs: number
): number
// ❌ Vague, abbreviated
async function valJson(s: any, i: string): Promise<any>
function calcBackoff(n: number, d: number): number

Rules:

  1. Be specific: activeUsers not users, httpTimeoutMs not timeout
  2. Include units: delayMs not delay, maxRetries not max
  3. Avoid abbreviations: customer not cust, configuration not cfg
  4. Use domain language: Names from business domain, not technical abstractions
  5. Boolean prefixes: isValid, hasPermission, canEdit, shouldRetry
  6. Verbs for functions: validateEmailFormat() not checkEmail(), fetchActiveUsers() not getUsers()

Function Design

Single Responsibility with Explicit Contracts

// ✅ Self-contained, explicit dependencies, typed contract
async function authenticateUser(
credentials: UserCredentials,
database: Database,
currentTime: DateTime
): Promise<Result<AuthSession, AuthError>> {
// All dependencies visible in signature
// Return type reveals all possible outcomes
}
// ❌ Hidden dependencies, unclear contract
async function auth(data: any): Promise<any> {
// Uses global config, modifies global state
}

Guard Clauses Over Nesting

Handle edge cases first, keep the happy path unindented and visible.

// ✅ Guard clauses — happy path clear
function processOrder(order: Order): Result<Receipt, ProcessError> {
if (!order) return err('missing_order');
if (order.items.length === 0) return err('empty_order');
if (order.total <= 0) return err('invalid_total');
if (!order.paymentMethod) return err('missing_payment');
return ok(completePayment(order));
}
// ❌ Nested conditions — happy path buried
function processOrder(order: Order) {
if (order) {
if (order.items.length > 0) {
if (order.total > 0) {
// Happy path buried 4 levels deep
}
}
}
}

Design Rules

  1. Single responsibility — describable in one sentence
  2. Explicit dependencies — all inputs as parameters, no hidden global state
  3. Type everything — TypeScript strict mode, Python type hints
  4. Self-contained context units — comprehensible without reading other files
  5. 50-line guideline — not a hard limit, but a refactoring trigger

Error Handling

Result Types — Make Errors Explicit

Errors belong in function signatures, not hidden behind throw.

type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
type UserError = 'not_found' | 'unauthorized' | 'network_failure';
async function fetchUser(id: string): Promise<Result<User, UserError>> {
// Errors are part of the contract
}
// Usage forces error handling — compiler catches missing cases
const result = await fetchUser(userId);
if (!result.ok) {
switch (result.error) {
case 'not_found': return show404();
case 'unauthorized': return redirectLogin();
case 'network_failure': return showRetry();
}
}

When to use Result types: API calls, file I/O, validation, any complex error path. When to use exceptions: Truly exceptional/unrecoverable situations (out of memory, corrupted state).

Branded Types — Validate at Boundaries

type ValidatedEmail = string & { readonly __brand: 'ValidatedEmail' };
type UserId = string & { readonly __brand: 'UserId' };
function validateEmail(input: string): ValidatedEmail | null {
return isValidEmail(input) ? (input as ValidatedEmail) : null;
}
// Type system prevents using unvalidated data
function sendEmail(to: ValidatedEmail, subject: string) {
// No need to re-validate — type guarantees validity
}

Once you have a ValidatedEmail, downstream functions carry zero validation overhead. The type system encodes the knowledge that validation occurred.

Error Principles

  1. Never silently swallow errors — log or propagate, never ignore
  2. Fail fast at boundaries — validate inputs immediately, not deep in call stack
  3. Provide actionable messages — what failed, expected vs actual, how to fix
// ✅ Actionable error with context
throw new ValidationError(
`Email validation failed for "user_email": ` +
`Expected "name@domain.com", received "${input}". ` +
`Use validateEmailFormat() to check before calling.`
);
// ❌ Opaque
throw new Error("Validation failed");

File & Module Organization

Structure with Clear Boundaries

// ========================================
// PUBLIC API
// ========================================
export class UserService {
constructor(private readonly db: Database) {}
async createUser(data: CreateUserData): Promise<Result<User, CreateError>> {
// Public interface
}
}
// ========================================
// VALIDATION
// ========================================
function validateUserData(data: unknown): Result<ValidatedData, ValidationError> {
// Grouped validation logic
}
// ========================================
// PRIVATE HELPERS
// ========================================
function hashPassword(password: string): Promise<HashedPassword> {
// Internal implementation
}

Organization Rules

  1. Group by feature/domain, not file type — authentication/, orders/, payments/
  2. Public API first — exported functions at top, helpers at bottom
  3. One major export per fileUserService.ts exports UserService
  4. Co-locate testsUserService.test.ts next to UserService.ts
  5. 300-line guideline — not a hard limit, but a refactoring trigger
  6. Minimal cross-module dependencies — each module is a clean context boundary
project/
├── authentication/ # Self-contained context
│ ├── index.ts # Public API only
│ ├── credentials.ts
│ ├── sessions.ts
│ └── README.md # Module architecture
├── orders/ # Independent context
└── storage/ # Independent context

Testing

Testing Trophy — Mostly Integration

"Write tests. Not too many. Mostly integration." — Kent C. Dodds

  1. Static Analysis (foundation): TypeScript strict mode, ESLint
  2. Unit Tests (narrow): Pure functions, complex algorithms
  3. Integration Tests (widest — most tests here): How pieces work together, where bugs actually live
  4. E2E Tests (top): Critical user journeys only

Tests as Documentation

Test names describe scenarios. Docstrings explain "why." Tests demonstrate usage.

test('should reject invalid credentials without revealing if username exists', async () => {
// Prevents username enumeration attacks
const auth = new Authenticator(database);
const result = await auth.authenticate({
email: 'nonexistent@example.com',
password: 'any-password'
});
expect(result.ok).toBe(false);
expect(result.error.code).toBe('INVALID_CREDENTIALS');
expect(result.error.message).not.toContain('user not found');
});

Testing Rules

  1. Test behavior, not implementation — focus on inputs/outputs, not internal state
  2. One concept per test — don't test multiple unrelated things
  3. Integration over unit — test pieces working together (more confidence per test, more resilient to refactoring)
  4. Clear test names — describe the scenario: test('user can add items to cart')
  5. 80% coverage minimum — focus on critical paths

Observability

Structured Logging

// ✅ Structured — queryable, correlated
logger.info('Request processed', {
request_id: requestId,
user_id: userId,
endpoint: req.path,
method: req.method,
duration_ms: duration,
status_code: res.statusCode,
cache_hit: cacheHit
});
// ❌ Unstructured — hard to query
logger.info(`User ${userId} accessed ${req.path}`);

What to Log

Always include: request_id, user_id, trace_id, entity IDs, operation type, duration_ms, error details.

Log at critical boundaries:

  • External API calls (request/response)
  • Database operations (query, duration)
  • Authentication/authorization decisions
  • Error occurrences with full context

One structured event per operation — derive metrics, logs, or traces from the same data. Don't instrument separately for each observability pillar.

Agentic Coding Patterns

These patterns address the unique demands of code that will be read, modified, and executed by AI agents alongside humans.

Idempotent Operations

Agents retry. Network calls fail. Tasks get re-run. Design every mutation to be safely repeatable.

// ✅ Idempotent — safe to retry
async function ensureUserExists(
email: ValidatedEmail,
db: Database
): Promise<User> {
const existing = await db.users.findByEmail(email);
if (existing) return existing;
return db.users.create({ email });
}
// ❌ Non-idempotent — duplicates on retry
async function createUser(email: string, db: Database): Promise<User> {
return db.users.create({ email });
}

Explicit State Machines Over Implicit Flows

When operations have distinct phases, model them explicitly. Agents reason about state machines far better than implicit status flags scattered across objects.

type OrderState =
| { status: 'draft'; items: Item[] }
| { status: 'submitted'; items: Item[]; submittedAt: DateTime }
| { status: 'paid'; items: Item[]; submittedAt: DateTime; paymentId: string }
| { status: 'shipped'; items: Item[]; trackingNumber: string };
// Each transition is a pure function with clear preconditions
function submitOrder(order: OrderState & { status: 'draft' }): OrderState & { status: 'submitted' } {
return { ...order, status: 'submitted', submittedAt: DateTime.now() };
}

Machine-Parseable Errors

Agents need structured errors alongside human-readable ones. Return error codes that can be programmatically matched, with messages that explain context.

type AppError = {
code: 'VALIDATION_FAILED' | 'NOT_FOUND' | 'CONFLICT' | 'UPSTREAM_TIMEOUT';
message: string; // Human-readable explanation
field?: string; // Which input caused it
retryable: boolean; // Can the caller retry?
};

Atomic, Independently-Verifiable Changes

Structure work so each change can be validated in isolation. This applies to commits, PRs, and function design. An agent (or reviewer) should be able to verify correctness without understanding the entire system.

// ✅ Each function is independently testable and verifiable
function parseConfig(raw: string): Result<Config, ParseError> { /* ... */ }
function validateConfig(config: Config): Result<ValidConfig, ValidationError[]> { /* ... */ }
function applyConfig(config: ValidConfig, system: System): Result<void, ApplyError> { /* ... */ }
// ❌ Monolithic — must understand everything to verify anything
function loadAndApplyConfig(path: string): void { /* 200 lines */ }

Convention Over Configuration

Reduce the search space for agents (and humans). Consistent patterns mean less context needed per decision.

  • Consistent file naming: UserService.ts, UserService.test.ts, UserService.types.ts
  • Predictable directory structure across features
  • Standard patterns for CRUD operations, API endpoints, error handling
  • If your project has a pattern, follow it. If it doesn't, establish one and document it

Contract-First Design

Define types before implementation. Types are the cheapest, most scannable form of documentation. An agent reading your types understands your system's data flow without reading a single function body.

// Define the contract first
interface OrderService {
create(data: CreateOrderInput): Promise<Result<Order, CreateOrderError>>;
cancel(id: OrderId, reason: CancelReason): Promise<Result<void, CancelError>>;
findByUser(userId: UserId, pagination: Pagination): Promise<PaginatedResult<OrderSummary>>;
}
// Then implement — the types guide everything

Observable Side Effects

Every mutation should produce structured output describing what changed. This enables agents to verify their actions and enables humans to audit.

type MutationResult<T> = {
data: T;
changes: Change[]; // What was modified
warnings: string[]; // Non-fatal issues encountered
};
async function updateUserProfile(
id: UserId,
updates: ProfileUpdates
): Promise<Result<MutationResult<UserProfile>, UpdateError>> {
// Returns both the result AND a description of what changed
}

Context Optimisation & Token Economics

Every token an agent reads is a token it can't use for reasoning. Treat context like memory in an embedded system — budget it, measure it, and refuse to waste it.

The Context Budget

AI agents operate within fixed context windows. Your code, documentation, error messages, and tool outputs all compete for the same finite space. Code that is token-efficient isn't just neat — it directly improves agent reasoning quality.

Context Window (finite)
├── System prompt & instructions ~2-5k tokens (fixed cost)
├── Conversation history ~variable
├── Tool definitions ~1-10k tokens (per tool schema)
├── Retrieved code / docs ~variable ← YOU CONTROL THIS
├── Agent reasoning ~variable ← THIS GETS SQUEEZED
└── Output generation ~variable ← AND SO DOES THIS
The more tokens your code consumes, the less room the agent has to think. Optimise ruthlessly.

Semantic Compression

Collapse granular interfaces into high-level semantic operations. Instead of exposing every low-level action, expose intent-based APIs.

// ❌ 15 granular tools = ~15k tokens of schema// An agent must read and reason about ALL of them
tools: [createFile,readFile,deleteFile,moveFile,copyFile,listDirectory,createDirectory,deleteDirectory,getFileMetadata,setFilePermissions,watchFile,compressFile,decompressFile,hashFile,diffFiles]// ✅ 1 semantic dispatcher = ~1k tokens of schema// Agent reasons about intent, not mechanics
tools: [{name: "filesystem",description: "Manage files and directories",parameters: {operation: "create | read | delete | move | copy | list | ...",path: "string",options: "object (operation-specific)"}}]

This is the dispatcher pattern: consolidate related tools behind a single entry point that routes by intent. Token cost drops dramatically while functionality stays the same.

Layered Context Loading

Don't front-load everything. Provide summaries first, with drill-down paths for when the agent actually needs more detail.

// ✅ Layered: summary first, details on demandfunctiongetProjectOverview(): ProjectSummary{return{name: "DataPipeline",modules: ["ingestion","transform","export"],entryPoint: "src/main.ts",recentChanges: getRecentChangeSummary(5),// Drill-down references — agent only loads what it needsgetModuleDetail: (name: string)=>loadModuleContext(name),getFileContent: (path: string)=>loadFileContext(path),};}// ❌ Eager: dumps everything into context upfrontfunctiongetProjectContext(): FullProjectDump{return{allFiles: readAllFiles(),// 50k tokensallTests: readAllTests(),// 30k tokensallDocs: readAllDocs(),// 20k tokens// Agent's context window is now full before it starts thinking};}

Token-Aware Documentation

Write documentation that serves both human readers and token budgets. Every word should earn its place.

# ❌ Token-heavy: narrative style, repetitive, verbose## Overview of the Authentication Module
The authentication module is responsible for handling all aspects of user authentication within our application. This module was designed with security best practices in mind and implements industry-standard protocols. The module handles user login, token generation, session management, and token refresh functionality. It is important to note that this module uses JWT tokens for authentication purposes.
(~80 tokens to say what could be said in 15)
# ✅ Token-efficient: dense, scannable, no filler## Authentication
JWT-based auth with refresh token rotation.
- Entry: `authenticate()``Result<Session, AuthError>`- Tokens: 15min access, 7d refresh (HTTP-only cookie)
- Storage: PostgreSQL users, Redis token blacklist
(~40 tokens, more information conveyed)

Structured Output for Agent Consumption

When building tools or functions that agents will consume, prefer structured, parseable output over human-readable prose.

// ✅ Agent-friendly: structured, parseable, minimaltypeBuildResult={success: boolean;errors: {file: string;line: number;code: string;message: string}[];warnings: {file: string;line: number;code: string;message: string}[];stats: {duration_ms: number;filesProcessed: number};};// ❌ Human-only: requires parsing natural languagefunctiongetBuildOutput(): string{return`Build completed with 2 errors and 1 warning. Error in src/auth.ts line 42: Type 'string' is not assignable... Error in src/orders.ts line 18: Property 'id' does not exist... Warning in src/utils.ts line 7: Unused variable 'temp'... Build took 3.2 seconds, processed 47 files.`;}

Context Boundaries as Architecture

Design modules so an agent can work within one module without loading others. Each module should be a self-contained context unit.

// ✅ Clean context boundary — agent only needs this module// payments/index.tsexportinterfacePaymentService{charge(input: ChargeInput): Promise<Result<Payment,PaymentError>>;refund(id: PaymentId,reason: RefundReason): Promise<Result<Refund,RefundError>>;}// payments/types.ts — all types co-located, no external dependenciesexporttypeChargeInput={amount: Money;method: PaymentMethod;idempotencyKey: string;// Agent-friendly: built-in retry safety};// payments/errors.ts — exhaustive, machine-readableexporttypePaymentError=|{code: 'INSUFFICIENT_FUNDS';available: Money}|{code: 'CARD_DECLINED';reason: string;retryable: false}|{code: 'GATEWAY_TIMEOUT';retryable: true};
// ❌ Leaky context boundary — agent must load 4 modules to understand 1
// payments/index.ts
import { User } from '../users/types';
import { Order } from '../orders/types';
import { AuditLogger } from '../audit/logger';
import { ConfigManager } from '../config/manager';
// Agent now needs context from users/, orders/, audit/, config/

Compression Strategies Reference

StrategyBeforeAfterSavings
Semantic dispatchersN tool schemas (~N × 1k tokens)1 dispatcher (~1k tokens)~(N-1)k tokens
Layered loadingFull dump (50k tokens)Summary + drill-down (2k + on-demand)~48k idle tokens
Dense docsNarrative prose (~80 tokens/concept)Structured bullets (~40 tokens/concept)~50%
Co-located typesScattered across modulesSingle types.ts per moduleFewer file loads
Summary-first returnsFull object graphsSummary + reference IDs60-90% per call
Discriminated unionsGeneric error + message stringTyped union with code fieldEliminates parsing

Project Navigation

CLAUDE.md at Project Root

Every project needs a navigation file. List entry points, patterns, and common tasks.

# Project: Data Processing Pipeline
## Entry Points
- `src/main.ts`: CLI interface
- `src/api/server.ts`: REST API
- `src/processors/pipeline.ts`: Core processing
## Key Patterns
- All processors implement `Processor` interface (src/processors/base.ts)
- Config uses Zod schemas (src/config/schemas.ts)
- External APIs via `APIClient` (src/external/client.ts)
## Common Tasks
- Add data source → implement `DataSource` in `src/api/sources/`
- Add transformation → implement `Transformer` in `src/processors/transformers/`

Keep under 200 lines. Update when architecture changes.

Module-Level READMEs

Every major directory gets a README answering: What is this? How does it work? What are the gotchas?

# Module: User Authentication
## Purpose
JWT-based authentication with refresh token rotation
## Key Decisions
- bcrypt cost factor 12 for password hashing
- Access tokens expire after 15 minutes
- Refresh tokens stored in HTTP-only cookies
## Dependencies
- jose library for JWT (not jsonwebtoken — more secure)
- PostgreSQL for user storage
- Redis for token blacklist

Progressive Context Hierarchy

  1. CLAUDE.md / README.md at root — system overview, entry points, setup
  2. README.md per major module — module purpose, key decisions, patterns
  3. Section comments in files — group related code with clear headers
  4. Function/class docs — purpose, examples for non-obvious APIs
  5. Inline comments — only for "why" decisions

Anti-Patterns

  • Premature optimization — Measure first, optimize second
  • Hasty abstractions — Wait for 3rd duplication before extracting
  • Clever code — Simple and obvious beats clever and compact
  • Silent failures — Log and propagate, never swallow
  • Vague interfacesprocess(data: any): any provides zero guidance
  • Hidden dependencies — Global state, singletons, ambient imports
  • Nested conditionals — Use guard clauses instead
  • Comments describing "what" — If you need a comment to explain what code does, rename things
  • Premature generalization — Build for today's requirements, not hypothetical futures
  • Token bloat — Functions returning everything when callers need summaries
  • Inverted disclosure — Helpers at top, public API buried at bottom
  • Flat files — 500-line files with no visual hierarchy, grouping, or section comments
  • Leaky context boundaries — Modules that import heavily from siblings, forcing agents to load the entire codebase
  • Eager context loading — Dumping full project state into agent context when a summary would suffice

Checklist

Before submitting code:

  • Solves the stated problem with minimal code?
  • A new developer can understand it without extensive context?
  • Errors handled with actionable messages?
  • Names clear, specific, and unambiguous?
  • Functions have single, clear responsibilities?
  • Dependencies explicit (no hidden global state)?
  • Tests cover critical paths?
  • Operations idempotent where applicable?
  • Types define contracts before implementation?
  • Would this work well with ~200 lines of surrounding context?
  • Can an agent understand this module without loading adjacent modules?
  • Are public APIs scannable in under 50 lines?
  • Do tool/function outputs use structured types, not prose?
  • Is documentation token-dense (no filler words, no repetition)?
  • Does the file follow progressive disclosure (types → public API → helpers)?

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

About

A quick reference to the living codestyle guidelines I feed to my AI agents, to help maintain scalable repositories.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors