Skip to content

Instantly share code, notes, and snippets.

@alpham8
Last active March 11, 2026 08:39
Show Gist options
  • Select an option

  • Save alpham8/3355c34aebeaf16b0dadc7a9c2ec8384 to your computer and use it in GitHub Desktop.

Select an option

Save alpham8/3355c34aebeaf16b0dadc7a9c2ec8384 to your computer and use it in GitHub Desktop.
My global CLAUDE.md

Global CLAUDE.md — Engineering Baseline

Project-local files always override this baseline: CLAUDE.md · AGENTS.md · README* · docs/ · code-conventions.md · .editorconfig


0) Always Start Here

  1. Read all project-local rules before touching code.
  2. Read the project's README, package.json scripts, composer.json scripts, or Makefile before running any build/test command. Never guess commands.
  3. Follow existing patterns unless they violate this baseline or a refactor is explicitly requested.
  4. Keep diffs small, focused, and easy to review.
  5. Do not introduce new dependencies or tools unless required or explicitly requested.
  6. For large projects, keep root CLAUDE.md lean. Move detailed domain knowledge into separate files (e.g. agent_docs/, .claude/skills/) and reference them from the root file.

1) Coding Standard: PSR-12 Across All Languages

PSR-12 is the universal style reference. Where it is PHP-specific, apply its intent to every other language consistently.

Rule Value
Indentation 4 spaces — no tabs
Max line length 120 characters
Line endings LF (Unix)
Encoding UTF-8 without BOM
File ending Single newline at end of file
Trailing whitespace Never
Imports / includes Ordered, no unused entries
Braces Consistent, readable — no brace gymnastics
Operators / keywords Consistent whitespace around them
One action per line No clever dense one-liners

If a language's tooling cannot reproduce PSR-12 brace placement exactly, enforce the closest consistent equivalent and prioritize readability and consistency.

Brace Placement in JavaScript / TypeScript (PSR-12 Intent)

Opening braces for classes, methods, and functions go on their own line — matching PHP PSR-12 brace placement. Control structures (if, for, while, etc.) keep the opening brace on the same line.

// ❌ Bad — opening brace on same line as function/class declaration
class UserService {
    public getUser(id: number): User {
        if (id <= 0) {
            throw new Error('Invalid ID');
        }

        return this.repository.find(id);
    }
}

// ✅ Good — opening brace on own line for class, method, function
class UserService
{
    public getUser(id: number): User
    {
        if (id <= 0) {
            throw new Error('Invalid ID');
        }

        return this.repository.find(id);
    }
}

This applies to all standalone function declarations, class declarations, and method definitions. Arrow functions and callbacks are exempt — they follow standard JS/TS conventions.


2) Security (Non-Negotiable)

Never trust user input. Always validate input and escape output where applicable.

Core Principles

  • Input validation everywhere: Validate all user input on both frontend and backend. Validate type, length, format, and allowed values. Reject invalid input early with guard clauses.
  • Output escaping: Escape all user-controlled data before rendering in HTML, JavaScript, SQL, shell commands, or any other output context.
  • Secure database queries: In any database context (relational, graph, document, etc.), always use the secure query mechanisms provided by the database driver — prepared statements, parameterized queries, ORM/ODM query builders, or equivalent. For graph databases (e.g. Neo4j), use parameterized Cypher queries. Never concatenate user input into query strings, regardless of database technology.
  • Injection prevention: Avoid any form of injection — SQL injection, command injection (never pass user input to shell commands), XSS, LDAP injection, template injection, header injection, etc.
  • API input validation: Validate and escape all data received from public APIs and webhooks. External data is untrusted data.
  • Frontend + backend validation: If there is a frontend and backend, validate and escape user input on both sides. Frontend validation is for UX; backend validation is for security. Never rely on frontend validation alone.
  • Rate limiting: Use API rate limiting mechanisms on all public-facing endpoints (authentication, webhooks, API routes) to prevent abuse and brute-force attacks.
  • Token / secret handling: Encrypt sensitive tokens at rest (e.g. OAuth tokens). Never log secrets or include them in error messages. Never commit secrets to version control.
  • CSRF protection: Use CSRF tokens on server-rendered HTML forms (login, contact, admin panels). For JSON APIs, CSRF tokens are unnecessary and counterproductive — instead rely on SameSite=Lax cookies, Origin/Referer header validation, Content-Type: application/json enforcement, and custom auth headers (which browsers cannot set cross-origin). Never add token-based CSRF to pure API endpoints — it breaks parallel requests and creates chicken-and-egg problems with SPAs.
  • Authentication hardening: Log failed login attempts. Use constant-time comparison for secrets and tokens.

Quick Checklist

Area Rule
User input Validate type, length, format, allowed values
Database queries Use secure driver mechanisms (prepared statements, parameterized queries, etc.)
HTML output Escape with context-appropriate encoding
Shell commands Never pass user input; use safe APIs instead
API endpoints Rate limiting on all public routes
Webhooks Verify signatures, validate payload structure
Frontend forms Validate on client AND server
OAuth tokens Encrypt at rest, never log
Error messages Never expose internal details or stack traces to users

3) Strict Type System (Non-Negotiable)

Every variable, parameter, return value, and field must have an explicit, precise type. Implicit or inferred types are only acceptable where the type is unambiguous and the language makes inference the idiomatic norm (e.g. const x = 1 in TypeScript is number — fine). Ambiguous cases always get an explicit annotation.

Quick Reference by Language

Language Rule Example
TypeScript No any. No var. const by default, let only when reassignment is needed. Explicit return types on exported functions. const count: number = 1;
JavaScript No var. Prefer const. Migrate to TypeScript when feasible. const label = 'active';
PHP declare(strict_types=1); in every file. Native type declarations on params, returns, properties. Docblocks only as supplements. function add(int $a, int $b): int
C# / .NET Prefer explicit types to var. Respect nullable reference types. Never suppress nullable warnings without justification. int count = 1;
Python Type hints on all function signatures. Use mypy or pyright in strict mode where the project allows. def add(a: int, b: int) -> int:
Bash / Shell Declare variable types with declare -i (integer), -r (readonly), -a (array) where applicable. Always quote variables. declare -i count=1
SQL Explicit column types in DDL. Never use SELECT * in application queries. created_at TIMESTAMP NOT NULL
Vue / Svelte TypeScript as source language, compiled to JavaScript by the bundler. <script setup lang="ts"> / <script lang="ts">. No TS in HTML templates. see Section 8

TypeScript: Additional Rules

  • catch (e: unknown) — always narrow safely before using e.
  • unknown + narrowing instead of any.
  • Generics over loose union types where appropriate.
  • Exported / public APIs must always have explicit types.
  • No implicit any via tsconfig ("strict": true minimum).

Null Avoidance (Non-Negotiable)

Never return null when a neutral empty value exists.

  • Return empty arrays [], empty strings '', or zero values instead of null.
  • Constructor dependencies must always be required (non-nullable). No ?Type $dep = null for injected services.
  • Nullable is only acceptable when null carries a genuinely distinct semantic meaning that an empty value cannot represent.
Language Do Don't
PHP function find(): array function find(): ?array
PHP private CacheInterface $cache private ?CacheInterface $cache = null
TypeScript function find(): Post[] function find(): Post[] | null
Python def find() -> list[Post] def find() -> Optional[list[Post]]

Immutability by Default

  • Use readonly properties (PHP 8.1+), const (JS/TS), final (Java/PHP classes not designed for extension).
  • Prefer constructor promotion with readonly for value objects and DTOs.
  • Mutate only when genuinely required — prefer creating new instances over modifying existing ones.
// ✅ Good — immutable value object
final class Money
{
    public function __construct(
        public readonly int $amount,
        public readonly string $currency,
    ) {
    }
}

Enums over Magic Values

Use language-native enums instead of string or integer constants for finite sets of values.

// ❌ Bad
const STATUS_ACTIVE = 'active';
const STATUS_INACTIVE = 'inactive';

// ✅ Good
enum Status: string
{
    case Active = 'active';
    case Inactive = 'inactive';
}

Type-Safe Comparisons (Non-Negotiable)

Never use empty() in PHP. It hides type information and silently coerces values. Always use explicit, type-safe checks instead.

Type Do Don't
Array count($items) > 0 / count($items) === 0 empty($items) / !empty($items)
String $name !== '' / $name === '' empty($name) / !empty($name)
Int / Float $count !== 0 / $count > 0 empty($count)
Bool $flag === true / $flag === false empty($flag)
Null check $value === null / $value !== null empty($value)

Always use strict comparison (===, !==). Never use loose comparison (==, !=).

// ❌ Bad — hides type, unclear intent
if (!empty($posts)) { … }
if (empty($title)) { … }

// ✅ Good — explicit, type-safe
if (count($posts) > 0) { … }
if ($title === '') { … }

This applies to all languages: always use the most specific, type-aware comparison available rather than loose truthiness checks.


4) Clean Code (Non-Negotiable)

Readability over cleverness — always.

  • Guard clauses first: Return early to avoid deep nesting.
  • Single responsibility: One function does one thing.
  • Small functions: A function should fit on one screen (~30–40 lines maximum). If it does not, split it.
  • No magic numbers or strings: Use named constants or enums.
  • No premature abstraction: Add layers only when they demonstrably reduce complexity.
  • Meaningful names: Variables, functions, and classes must reveal intent. No unexplained abbreviations.
  • No dead code: Remove commented-out code; use version control instead.
  • get vs find naming: getX() expects the result to exist and throws on failure. findX() is a lookup that may return an empty result (empty array, empty string). Never mix these semantics.
  • Boolean method names: Methods returning bool must start with is, has, can, should, or was — e.g. isActive(), hasPermission(), canEdit().
// ❌ Bad
function p(u: any, t: string) {
    if (u !== null) { if (u.active) { if (t === 'x') { return 42; } } }
}

// ✅ Good
const PERMISSION_DENIED = 403;

function resolvePermission(user: User, type: PermissionType): number
{
    if (!user.active) {
        return PERMISSION_DENIED;
    }

    if (type !== PermissionType.Admin) {
        return PERMISSION_DENIED;
    }

    return HttpStatus.Ok;
}

5) Low Complexity

Keep cyclomatic complexity low. Aim for ≤ 5 per function, hard limit 10 — if a function exceeds this, refactor before adding more logic.

  • Prefer flat over nested.
  • Prefer composition to inheritance where it reduces branching.
  • Avoid boolean flag parameters — use separate functions or strategy patterns instead.
  • Avoid deeply chained calls that obscure control flow.
  • Maximum 4 parameters per function/method. Beyond that, introduce a value object, DTO, or options object.
  • No mixed return types: A method must always return the same type. Never return string sometimes and array other times.
// ❌ Bad — boolean flag parameter
function render(component: Component, isAdmin: boolean) {  }

// ✅ Good — separate, explicit functions
function renderAdminView(component: Component) {  }
function renderUserView(component: Component) {  }

6) Code Maintainability

Architecture

  • Thin controllers / routes: Business logic belongs in services or use-cases.
  • DTOs / ViewModels at I/O boundaries: Never expose persistence entities directly to the outside.
  • Errors are explicit and meaningful: Never silently swallow exceptions. Log or re-throw with context.
  • Constructor discipline: Constructors assign dependencies — nothing else. No business logic, no I/O, no HTTP calls. Pure configuration (e.g. setting up a converter) is acceptable.
  • All dependencies required: Every injected service must be non-optional. If a class needs a dependency, it must always receive one — no fallback behaviour on missing services.
  • Specific exceptions: Throw domain-specific exceptions (e.g. PostNotFoundException), not generic \RuntimeException or \Exception. Catch specific exceptions, never bare catch (\Exception $e) unless re-throwing.

Documentation

  • Public APIs and non-obvious functions require a concise docblock explaining why, not what.
  • Inline comments explain intent, not mechanics. Do not comment the obvious.
  • Keep comments up to date with the code — stale comments are worse than none.

Refactoring Rules

  • Only refactor as part of a focused, clearly scoped change.
  • Never mix refactoring with feature work in the same commit/diff.
  • Leave the code measurably better than you found it (Boy Scout Rule).

7) JavaScript / TypeScript Tooling Defaults

Prettier

{
    "printWidth": 120,
    "tabWidth": 4,
    "useTabs": false,
    "semi": true,
    "singleQuote": true,
    "trailingComma": "all",
    "bracketSpacing": true,
    "endOfLine": "lf"
}

ESLint (enforce PSR-12 intent)

  • Forbid var
  • Prefer const
  • Forbid any (allow unknown)
  • Enforce removal of unused imports and variables
  • Require explicit types on all exported / public module boundaries

If a project already has ESLint + Prettier configured, do not fight the existing setup. Adjust it minimally to meet the rules above.


8) Vue & Svelte

  • TypeScript is the source language — it must always be compiled down to JavaScript. No raw TypeScript is ever shipped or embedded directly in HTML.
  • Component script blocks use <script setup lang="ts"> (Vue) / <script lang="ts"> (Svelte) so the bundler/compiler (Vite, SvelteKit, etc.) handles the TS → JS compilation step.
  • The compiled output (.js) is what gets delivered to the browser — never .ts files directly.
  • No plain .js component source files — TypeScript is always the authoring language.
  • Props, emits, and exposed values must have explicit TypeScript types.
  • Composables (Vue) / stores (Svelte) follow the same strict typing rules as any other module.
  • No TypeScript syntax inside HTML template blocks (<template> / markup) — types live exclusively in the <script> block.

TypeScript Decorators

Decorators are allowed and encouraged where they reduce boilerplate or complexity (e.g. dependency injection, class-based components, ORMs).

Rules:

  • Do not mix decorator modes (legacy experimentalDecorators vs. TC39 stage-3) within a project.
  • Align tsconfig and bundler settings to the project's chosen decorator mode consistently.
  • Decorators must not hide critical business logic — keep decorated classes readable without knowing the decorator internals.

9) Testing & Definition of Done

  • New or changed core behavior requires tests (unit and/or integration as appropriate).

Unit Tests: No Real API Calls (Non-Negotiable)

Unit tests must never call real external APIs. Always use mocked, stubbed, or faked responses that simulate the API's behavior — including edge cases and error scenarios.

  • Mock or stub every external dependency (HTTP clients, SDKs, database connections, file systems, mail services).
  • Use realistic but fictional response data that mirrors the actual API response structure.
  • Always cover edge cases with fictional responses: empty results, error codes (4xx, 5xx), rate limits, timeouts, malformed responses, partial data, pagination boundaries.
  • The test suite must run fully offline, fast, and deterministically — no network access, no external state.
// ❌ Bad — calls real API
$response = $httpClient->request('GET', 'https://api.example.com/posts');

// ✅ Good — mocked response with realistic structure
$httpClient = $this->createMock(HttpClientInterface::class);
$httpClient->method('request')->willReturn(
    new MockResponse(json_encode(['posts' => [], 'total' => 0]), ['http_code' => 200])
);

This rule applies to all languages and frameworks — PHP, TypeScript, Python, Bash, etc.

  • Done means:
    • Typecheck passes (tsc, mypy, phpstan, etc.)
    • Build passes with no new errors
    • All tests pass
    • No new linter warnings
    • Formatting compliant (Prettier / PSR-12 equivalent)
    • No secrets, credentials, or debug output committed
    • End-to-end tests pass
    • All interactive UI elements are within the visible viewport and reachable by the user (no off-screen, hidden, or unreachable controls)
    • No N+1 queries or unbounded result sets introduced (see Section 12)
    • Migrations are reversible — updownup verified (see Section 13)
    • Accessibility: keyboard navigable, labels present, contrast sufficient (see Section 16)
    • User-visible strings use translation keys, not hardcoded text (see Section 17)
    • Commit messages follow Conventional Commits format (see Section 18)

10) Environment

  • If a project uses DDEV (.ddev/ exists): run PHP / Composer / Node commands inside the container via ddev exec … or ddev ssh, unless project docs say otherwise.
  • Do not install global tools on the host if the project provides a containerised equivalent.

11) Boundaries (Non-Negotiable)

Explicit autonomy limits. These override any implicit assumptions.

Always Do

  • Run linter, formatter, and typecheck before considering work done.
  • Run tests before committing.
  • Work in src/, tests/, app/, lib/ and other source directories.
  • Read existing tests and copy their style when writing new ones.

Ask First

  • Adding or removing dependencies.
  • Database schema changes or new migrations.
  • Changing CI/CD pipeline configuration.
  • Modifying shared infrastructure, deployment configs, or environment files.
  • Deleting files outside the immediate scope of the task.
  • Changing authentication or authorization logic.

Never

  • Commit secrets, credentials, API keys, or .env files.
  • Modify vendor/, node_modules/, or other managed directories.
  • Edit production configuration files directly.
  • Bulk-update lockfiles (composer update / npm update without a specific package).
  • Force-push to main or master.

12) Performance (Non-Negotiable)

Write code that performs well by default. Fix performance problems when you see them.

Database

  • No N+1 queries: Never execute database queries inside loops. Use eager loading, joins, or batch fetches.
  • Index awareness: Every WHERE, JOIN, and ORDER BY column should have an index or a documented reason why not.
  • Limit result sets: Always paginate or limit queries that could return unbounded rows.

Algorithmic Complexity

  • Flag O(n^2) and worse: If a nested loop operates on collections that grow with input, use hash maps, sets, or sorted structures instead.
  • Avoid redundant computation: Do not recalculate values inside loops that could be computed once outside.

Frontend / Bundle

  • Lazy load routes, heavy components, and images that are not immediately visible.
  • Code split vendor libraries from application code.
  • No unoptimised assets: Compress images, use modern formats (WebP/AVIF), set appropriate cache headers.

Memory

  • Clean up subscriptions: Remove event listeners, clear timers, and unsubscribe observables in component teardown / destructors.
  • Avoid unbounded caches: Every in-memory cache must have a maximum size or TTL.
  • Stream large data: Process large files, datasets, or API responses as streams — never load entirely into memory.

13) Database & Migration Discipline

Migrations

  • One purpose per migration: Each migration does exactly one thing. Name it descriptively: add_user_email_index, create_orders_table, drop_legacy_status_column.
  • Bidirectional required: Every migration must include both up and down (or equivalent rollback). If a rollback is genuinely impossible, document why in a comment inside the migration.
  • No data manipulation in structural migrations: Separate schema changes from data transformations.
  • Test migrations: Run up then down then up again to verify idempotency before committing.

Schema Design

  • Explicit column types: Every column has a precise type and nullability constraint.
  • Foreign keys enforced: Use database-level foreign key constraints, not just application-level checks.
  • Timestamps by default: Include created_at and updated_at on every table unless there is a documented reason not to.
  • Soft deletes: Prefer a deleted_at timestamp over hard deletes for user-facing data. Hard deletes are acceptable for ephemeral or system-internal data.

14) Dependency Management (Non-Negotiable)

Adding Dependencies

  • Verify existence: AI tools sometimes hallucinate package names. Always verify a package exists before adding it.
  • Evaluate before adding: Check maintenance activity, security history, dependency footprint, and community health. Prefer well-maintained packages with few transitive dependencies.
  • Pin exact versions for application dependencies. Use ranges only for libraries intended for redistribution.
  • One dependency per commit: Adding a dependency is its own change — never bundle it with feature work.

Updating Dependencies

  • Never bulk-update: Always update one package at a time with an explicit version (composer require vendor/package:^2.1, npm install package@4.2.0).
  • Read changelogs: Before updating, check the changelog for breaking changes.
  • Lockfile discipline: Never manually edit lockfiles (composer.lock, package-lock.json, yarn.lock). They are generated artifacts.

Removing Dependencies

  • Verify unused: Search the entire codebase for imports/usages before removing a dependency.
  • Remove completely: Remove from manifest, lockfile (via tool), and any configuration or documentation that references it.

15) Observability & Logging

Structured Logging

  • JSON format: Use structured JSON logging in all backend services. Never use unstructured string concatenation for log messages.
  • Correlation IDs: Every request must carry a correlation ID (trace ID) that propagates through all service calls and appears in every log entry.
  • Standard fields: Every log entry must include at minimum: timestamp, level, component, message, correlationId.
  • Log levels: Use levels consistently — error for failures requiring attention, warn for degraded but functional states, info for significant business events, debug for development diagnostics (never in production).

Data Safety in Logs

  • Never log secrets: Passwords, tokens, API keys, session IDs, and encryption keys must never appear in logs.
  • Sanitise PII: Mask or redact personally identifiable information (email, phone, IP, names) before logging. Log user IDs instead of personal data.
  • No request/response body dumps: Log metadata (status, duration, endpoint) — not full payloads.

Error Tracking

  • Attach context: Error reports must include correlation ID, user ID (anonymised), breadcrumbs (recent actions), and relevant metadata.
  • Separate environments: Use distinct error tracking projects/channels for development, staging, and production.
  • Source maps: Enable source maps in error tracking for compiled/minified code so stack traces are readable.

16) Accessibility (Non-Negotiable for User-Facing UI)

Core Principles (WCAG 2.1 AA Minimum)

  • Semantic HTML first: Use native HTML elements (<button>, <nav>, <main>, <table>, <label>) before reaching for <div> or <span> with ARIA roles.
  • Keyboard navigable: Every interactive element must be reachable and operable via keyboard alone. Never remove focus outlines (outline: none) without providing a visible alternative.
  • Labels and ARIA: Every form input must have an associated <label>. Use aria-label or aria-labelledby only when a visible label is not possible.
  • Alt text: Every <img> must have an alt attribute. Decorative images use alt="". Informative images describe the content.
  • Colour contrast: Text must meet WCAG AA contrast ratios (4.5:1 for normal text, 3:1 for large text). Never convey information through colour alone.
  • Focus management: After navigation, modal open/close, or dynamic content changes, move focus to the appropriate element.

Anti-Patterns to Reject

  • Custom widgets when a native HTML element does the job.
  • Hover-only interactions without keyboard/touch equivalents.
  • Colour as the sole indicator of state (error = red only, success = green only).
  • Disabled buttons without explanation — prefer keeping buttons enabled and showing validation on submit.
  • Auto-playing media without user consent.

17) Internationalisation (i18n)

Apply these rules whenever a project supports or may support multiple languages.

  • Key-based translations: Use identifiers (common.buttons.save), never embed user-visible strings as source text.
  • No string concatenation: Never build translated strings by concatenating parts. Use parameterised interpolation (Hello, {{name}}).
  • Pluralisation: Use the translation framework's plural rules (_zero, _one, _other or equivalent). Never use if count === 1 to switch strings.
  • Locale-aware formatting: Use Intl.NumberFormat, Intl.DateTimeFormat, or equivalent library functions for numbers, dates, and currencies. Never format manually.
  • CSS logical properties: Use margin-inline-start, padding-block-end, etc. instead of margin-left, padding-bottom to support RTL layouts.
  • No hardcoded locale assumptions: Date formats, number separators, currency symbols, and text direction must come from locale configuration — never hardcode them.
  • Translation coverage: Every user-visible string must have a translation key. Run coverage checks to detect missing keys across locales.

18) Git Workflow

Conventional Commits

All commit messages follow Conventional Commits:

type(scope): description

[optional body]
Type When to use
feat New feature or capability
fix Bug fix
docs Documentation only
refactor Code change that neither fixes a bug nor adds a feature
perf Performance improvement
test Adding or correcting tests
chore Build process, tooling, dependency updates
style Formatting, whitespace — no logic change
  • Subject line: Maximum 50 characters, imperative mood ("add", not "added" or "adds").
  • Body: Wrap at 72 characters. Explain why, not what.
  • Scope: Optional but encouraged. Use the module, component, or area name.

Branch Naming

type/short-description

Examples: feat/user-auth, fix/cart-total-rounding, refactor/order-service.

  • Lowercase, hyphens as separators.
  • Type prefix matches Conventional Commits types.
  • Keep it short but descriptive.

Workflow Rules

  • Never commit directly to main / master — use feature branches
  • squash your commits before merge to maintain a clear history (unless project conventions say otherwise)
  • use merge strategy instead of rebase most projects are intended for teamwork, so rebase would fail
  • One logical change per commit — do not mix unrelated changes
  • Delete branches after merge — no stale branches
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment