Coding standards locked in wikis get ignored within weeks. Coding.md puts your conventions, style guides, and best practices in markdown files that live alongside your code - where AI assistants enforce them automatically and developers reference them naturally.
Document your naming conventions, git workflow, code review checklist, and common gotchas in structured markdown. New developers absorb team standards in hours instead of weeks. AI assistants generate code that passes your linter on the first try.
The best engineering teams do not have better developers - they have better-documented standards that compound quality over time.
Transform implicit coding knowledge into explicit, versioned standards that AI assistants enforce and new developers absorb from day one.
Spell out every naming pattern your team uses - files, functions, variables, CSS classes, database tables. Naming inconsistency is the most visible code quality issue and the easiest to prevent with documented standards.
Capture what reviewers check for in every PR - error handling, test coverage, naming, security, performance. A documented checklist makes reviews consistent and helps AI assistants provide targeted feedback.
Document your branching strategy, commit message format, and merge process. Include examples of good and bad commit messages. Consistent git history is documentation that AI assistants can learn from.
Every codebase has traps - race conditions, timezone issues, null edge cases. Document them as a "gotchas" section. The knowledge that prevents bugs is too valuable to exist only in senior developers' heads.
Write your coding standards as if the reader is a smart developer who knows nothing about your project. This framing produces documentation that works for both new hires and AI assistants - they have the same needs.
For every standard, document why it exists. "Use UTC for all timestamps because our services span multiple timezones and local time causes join mismatches." Standards with reasoning get followed; arbitrary rules get ignored.
Standards that never change become stale. Schedule quarterly reviews to remove outdated rules, add patterns learned from incidents, and evolve conventions as your stack changes. Living standards earn trust.
Every standard should include a "do this" and "not this" code snippet. Developers and AI assistants learn patterns from examples faster than from prose descriptions. Show the standard in action.
A coding standard that lives in one developer's head helps one developer. A standard documented in markdown helps every developer and every AI assistant on the team, on every task, permanently. The 30 minutes you spend documenting a convention saves hours of code review discussions, onboarding confusion, and AI-generated rework. Standards are not bureaucracy - they are the highest-leverage documentation you can write.
# Coding.md - Development Standards and Guidelines
<!-- Coding standards, git workflow, code review, debugging, and error handling -->
<!-- Establishes team conventions for consistent, maintainable code -->
<!-- Last updated: YYYY-MM-DD -->
## Coding Standards
### Linting and Formatting Configuration
This project enforces consistent code style through automated tooling. All rules are non-negotiable - fix lint errors before committing, never disable rules inline without a PR-approved comment explaining why.
**ESLint Configuration** (`.eslintrc.cjs`):
```javascript
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/strict-type-checked',
'plugin:react-hooks/recommended',
'prettier', // Must be last - disables formatting rules that conflict with Prettier
],
rules: {
// Enforce explicit return types on exported functions
'@typescript-eslint/explicit-function-return-type': ['error', {
allowExpressions: true,
allowTypedFunctionExpressions: true,
}],
// No unused variables (prefix with _ to ignore intentionally)
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
}],
// Prefer nullish coalescing over logical OR for defaults
'@typescript-eslint/prefer-nullish-coalescing': 'error',
// No floating promises - must await or void
'@typescript-eslint/no-floating-promises': 'error',
// No console.log in production code (use logger instead)
'no-console': ['error', { allow: ['warn', 'error'] }],
// Enforce consistent import ordering
'import/order': ['error', {
groups: ['builtin', 'external', 'internal', 'parent', 'sibling'],
'newlines-between': 'always',
alphabetize: { order: 'asc' },
}],
},
};
```
**Prettier Configuration** (`.prettierrc`):
```json
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always"
}
```
**Running Lint Checks**:
```bash
npm run lint # Check all files for lint errors
npm run lint:fix # Auto-fix fixable issues
npm run format # Run Prettier on all files
npm run format:check # Check if files are formatted (CI use)
npm run type-check # TypeScript compiler check (no emit)
```
### General Principles
1. **Readability over cleverness** - Code is read 10x more than it is written. If a clever one-liner requires a comment to explain, write it as clear multi-line code instead.
2. **Explicit over implicit** - Name things clearly. Avoid abbreviations unless universally understood (e.g., `id`, `url`, `api`). A function named `processData` is worse than `transformOrderLineItems`.
3. **Single responsibility** - Each function does one thing. Each file owns one concept. If a function needs "and" in its description, split it.
4. **Fail fast and loud** - Validate inputs at the boundary. Throw descriptive errors. Never swallow exceptions silently.
5. **No dead code** - Delete unused functions, commented-out blocks, and obsolete files. Version control is your backup.
### Function Guidelines
```typescript
// Good: Clear name, typed parameters, single purpose, handles edge cases
export function calculateOrderTotal(
lineItems: OrderLineItem[],
discountPercent: number,
taxRate: number,
): Money {
if (lineItems.length === 0) {
return Money.zero();
}
const subtotal = lineItems.reduce(
(sum, item) => sum.add(item.price.multiply(item.quantity)),
Money.zero(),
);
const discount = subtotal.multiply(discountPercent / 100);
const taxable = subtotal.subtract(discount);
const tax = taxable.multiply(taxRate);
return taxable.add(tax);
}
// Bad: Vague name, untyped, does too much, no edge case handling
function process(data: any, flag: boolean) {
// 200 lines of mixed concerns
}
```
### Comments and Documentation
```typescript
// Use comments to explain WHY, not WHAT
// Good:
// We retry 3 times because the payment gateway occasionally returns 503
// during their nightly maintenance window (11 PM - 12 AM UTC)
const MAX_PAYMENT_RETRIES = 3;
// Bad:
// Set max retries to 3
const MAX_PAYMENT_RETRIES = 3;
/**
* Resolves the effective permission level for a user on a resource.
* Checks direct grants first, then walks up the folder hierarchy
* to find inherited permissions. Returns the highest permission found.
*
* @param userId - The user whose permissions to check
* @param resourceId - The document or folder to check against
* @returns The highest permission level, or null if no access
*/
export async function resolvePermission(
userId: string,
resourceId: string,
): Promise<PermissionLevel | null> {
// Implementation
}
```
## Error Handling
### Service Layer Errors
```typescript
// Define domain-specific error classes
export class NotFoundError extends Error {
constructor(entity: string, id: string) {
super(`${entity} with id '${id}' not found`);
this.name = 'NotFoundError';
}
}
export class ValidationError extends Error {
constructor(
message: string,
public readonly field: string,
public readonly value: unknown,
) {
super(message);
this.name = 'ValidationError';
}
}
export class PermissionDeniedError extends Error {
constructor(action: string, resource: string) {
super(`Permission denied: cannot ${action} on ${resource}`);
this.name = 'PermissionDeniedError';
}
}
// Use in service functions
export async function deleteDocument(id: string, userId: string): Promise<void> {
const doc = await getById(id);
if (!doc) throw new NotFoundError('Document', id);
if (doc.ownerId !== userId) throw new PermissionDeniedError('delete', `document/${id}`);
await db.delete(documents).where(eq(documents.id, id));
}
```
### API Layer Error Mapping
```typescript
// Map domain errors to HTTP/API responses consistently
function mapErrorToResponse(error: unknown): ErrorResponse {
if (error instanceof NotFoundError) {
return { status: 404, code: 'NOT_FOUND', message: error.message };
}
if (error instanceof ValidationError) {
return { status: 400, code: 'VALIDATION_ERROR', message: error.message };
}
if (error instanceof PermissionDeniedError) {
return { status: 403, code: 'FORBIDDEN', message: error.message };
}
// Unknown errors - log full details, return generic message to client
logger.error('Unhandled error', { error });
return { status: 500, code: 'INTERNAL_ERROR', message: 'An unexpected error occurred' };
}
```
### Async Error Handling
```typescript
// Always await promises - no floating promises
// Good:
await sendNotification(userId, message);
// Good (when you intentionally fire-and-forget):
void sendNotification(userId, message); // Explicit void signals intent
// Bad - floating promise, errors silently lost:
sendNotification(userId, message);
// Wrap external calls with timeout and retry
export async function callExternalApi<T>(
fn: () => Promise<T>,
options: { retries?: number; timeoutMs?: number } = {},
): Promise<T> {
const { retries = 3, timeoutMs = 5000 } = options;
for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await Promise.race([
fn(),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeoutMs),
),
]);
} catch (error) {
if (attempt === retries) throw error;
await sleep(Math.pow(2, attempt) * 100); // Exponential backoff
}
}
throw new Error('Unreachable');
}
```
## Git Workflow
### Branch Naming
```bash
feature/user-profile-photo-upload
bugfix/order-total-rounding-error
hotfix/stripe-webhook-signature-validation
refactor/extract-notification-service
docs/api-v3-migration-guide
test/add-payment-integration-tests
chore/upgrade-typescript-5.4
```
### Commit Messages
Follow conventional commits with a scope when useful:
```bash
feat(auth): add magic link login option
fix(orders): correct tax calculation for exempt items
refactor(notifications): extract email templates to separate module
docs(api): document rate limiting headers and behavior
test(payments): add integration tests for refund flow
perf(search): add trigram index for full-text document search
chore(deps): upgrade React to 18.3, fix breaking changes
```
### Pull Request Process
1. **Create PR** with the template filled out - title follows commit convention
2. **Link issue** using "Closes #123" in the description
3. **Self-review** - read your own diff before requesting reviews. Fix obvious issues first.
4. **Request reviewers** - 1 required from code owner, 1 from any team member
5. **Address feedback** - respond to every comment, even if just "Done" or "Good point, fixed"
6. **Wait for CI** - all checks must pass: lint, types, tests, build
7. **Squash merge** - PR title becomes the commit message on main
## Code Review Checklist
### Reviewer Checklist
- [ ] Does the code accomplish what the PR description says it does?
- [ ] Are there sufficient tests? Do they cover edge cases and error paths?
- [ ] Is error handling comprehensive? Are errors logged with enough context?
- [ ] Are there any security concerns? (SQL injection, XSS, auth bypass, exposed secrets)
- [ ] Is the code consistent with existing patterns in the codebase?
- [ ] Are new dependencies justified? Did the author evaluate alternatives?
- [ ] Is there any dead code, commented-out code, or TODO without a linked issue?
- [ ] Will this change cause problems for other features or teams?
- [ ] Is the database migration reversible? Does it handle existing data correctly?
- [ ] Are log messages helpful for debugging in production?
### Review Comment Prefixes
```
nit: minor style suggestion, non-blocking
suggestion: alternative approach worth considering, non-blocking
question: seeking clarification, may or may not be blocking
concern: potential problem that should be addressed before merge
blocker: must be fixed before approval
praise: calling out something done well (do this often!)
```
## Debugging Guide
### Local Debugging Setup
```bash
# Enable verbose application logging
LOG_LEVEL=debug npm run dev
# Run with Node.js inspector for breakpoint debugging
npm run dev:debug
# Then open chrome://inspect in Chrome and connect
# Run a single test file in watch mode for fast iteration
npm test -- --watch src/services/order.service.test.ts
```
### Common Issues
**Issue: "Module not found" after pulling latest**
```bash
# Dependency tree is out of sync - reinstall
rm -rf node_modules
npm install
```
**Issue: Type errors after branch switch**
```bash
# TypeScript cache may be stale
npx tsc --build --clean
npm run type-check
# In VS Code: Ctrl+Shift+P -> "TypeScript: Restart TS Server"
```
**Issue: Database migration failed**
```bash
# Check migration status
npm run db:migrate:status
# If stuck, check for lock
SELECT * FROM _migrations_lock;
# Reset lock if orphaned (after confirming no migration is running)
UPDATE _migrations_lock SET is_locked = false;
```
**Issue: Tests pass locally but fail in CI**
```bash
# Usually a timing issue or missing environment variable
# Run tests with CI-like settings locally:
CI=true npm test
# Check for timezone-dependent tests
TZ=UTC npm test
```
## Naming Conventions
| Context | Convention | Example |
|---------|-----------|---------|
| Files (components) | kebab-case | `order-summary.tsx` |
| Files (utilities) | kebab-case | `format-currency.ts` |
| Files (tests) | kebab-case + `.test` | `order-summary.test.ts` |
| React components | PascalCase | `OrderSummary` |
| Functions | camelCase | `calculateDiscount` |
| Variables | camelCase | `orderTotal` |
| Constants | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` |
| Types/Interfaces | PascalCase | `OrderLineItem` |
| Database tables | snake_case (plural) | `order_line_items` |
| Database columns | snake_case | `created_at` |
| API endpoints | kebab-case | `/api/v1/order-items` |
| Environment vars | UPPER_SNAKE_CASE | `DATABASE_URL` |
| CSS classes | kebab-case | `order-summary-card` |
| Git branches | kebab-case with prefix | `feature/order-export` |
## Performance Guidelines
### Code-Level Performance
- Use pagination for any list query - default page size of 20, max 100
- Debounce user input handlers (search, autocomplete) with 300ms delay
- Memoize expensive computations with `useMemo` (React) only when profiling shows it helps
- Use `Promise.all` for independent async operations instead of sequential `await`
- Avoid creating objects or arrays inside render functions - hoist to module scope or `useMemo`
### Database Performance
- Add indexes on columns used in WHERE, JOIN, and ORDER BY clauses
- Use `EXPLAIN ANALYZE` before deploying queries that touch large tables
- Never use `SELECT *` - list specific columns
- Use `LIMIT` on all queries that could return unbounded results
- Prefer batch inserts over loops of individual inserts
### Security Checklist
- [ ] All user input is validated with Zod schemas at the API boundary
- [ ] SQL queries use parameterized statements (never string concatenation)
- [ ] User-generated content is escaped before rendering (React handles this by default)
- [ ] Authentication is checked on every protected route and API endpoint
- [ ] Secrets are in `.env` files, never committed to version control
- [ ] Dependencies are audited regularly with `npm audit`
- [ ] File uploads are validated for type and size before processing
- [ ] Rate limiting is enabled on authentication and public API endpoints
Code standards locked in wikis are code standards ignored. Coding.md puts your conventions, patterns, and best practices in markdown files alongside your code. Version them. Review them. Let AI assistants enforce them automatically. Standards become living documentation that evolves with your codebase.
New developers shouldn't spend weeks learning unwritten rules. Coding.md documents your git workflows, debugging approaches, and common gotchas in structured markdown. AI assistants can instantly surface relevant guidance. What took weeks now takes hours. Knowledge transfer becomes deterministic.
AI-assisted code review needs your standards to be effective. Coding.md provides the context layer that transforms generic suggestions into specific, project-aligned feedback. Your quality bar is codified, versioned, and consistently applied. Every PR benefits from institutional knowledge.
"The difference between good code and great code is context. Coding.md helps teams capture and share the patterns, practices, and principles that define their engineering culture - making implicit knowledge explicit and available to both humans and AI."
Built by development teams who believe great code starts with great documentation.
We are committed to changing how teams think about coding standards. They shouldn't be locked in wikis or lost in Slack threads. Coding standards belong in .md files, versioned with your code, enforced by AI assistants, and evolved through pull requests. When standards live as markdown, they become living documentation that actually gets used.
Our vision is simple: every codebase should have its conventions, patterns, and best practices captured in markdown files that both humans and AI can understand. This isn't just documentation - it's the foundation of consistent, high-quality code at scale. Standards as infrastructure, not afterthought.
LLMs parse markdown better than any other format. Fewer tokens, cleaner structure, better results.
Context evolves with code. Git tracks changes, PRs enable review, history preserves decisions.
No special tools needed. Plain text that works everywhere. Documentation humans actually read.
Have coding standards you want to share? Questions about documentation best practices? Let's collaborate.