Skip to content

Instantly share code, notes, and snippets.

@zcaceres
Last active May 13, 2026 21:59
Show Gist options
  • Select an option

  • Save zcaceres/9af9cd00da3b7631b3e769770d135107 to your computer and use it in GitHub Desktop.

Select an option

Save zcaceres/9af9cd00da3b7631b3e769770d135107 to your computer and use it in GitHub Desktop.
Typescript CLAUDE.md boilerplate — My standard instructions for projects (zach.dev)

Communication

  • Be extremely concise. Sacrifice grammar for concision.
  • Avoid unnecessary words and tangential points. Stay on the question.
  • When asking the user one or more questions, enumerate them (1., 2., 3.) so each can be answered individually. For decision points, list options under each question:
    1. Which approach?
       - Option A: ...
       - Option B: ...
    2. Proceed? (yes/no)
    
  • Favor simple real code examples over long written explanations.
  • When discussing data, use schemas to illustrate shapes and types rigorously.
  • When discussing functionality, use function signatures and diagrams to illustrate flow.
  • Diagrams by surface:
    • Persistent docs (markdown files, Obsidian, READMEs, PRs, issues): mermaid — renders to SVG
    • Conversation / terminal / inline explanations: ASCII — renders natively, no viewer required
    • Never put mermaid in a chat reply where the user will read it as raw text.
  • ASCII conventions:
    Boxes:     [Client] ──REST──> [API] ──SQL──> [(DB)]
    Sequence:  Client → API:    POST /login
               API    → DB:     validate
               DB     → API:    user
               API    → Client: {token}
    Tree:      root
               ├── child-a
               │   └── grandchild
               └── child-b
    

Planning & Process

  • Always plan solution and confirm before executing.
  • Design code interfaces and confirm BEFORE writing code.
  • Database migrations / schema changes last — defer until implementation complete.

Scope Discipline — No Unrequested Hardening

Do not proactively add "defense in depth," "belt and suspenders," fallbacks, retries, redundant validation, or backward-compatibility shims unless I explicitly ask for them. Build exactly what was requested — nothing more.

  • No try/catch around code that can't fail, or that should fail loudly.
  • No fallback values for inputs that should never be missing — let it throw.
  • No coercing null/undefined to a semantically meaningful default (e.g. value ?? 0, count ?? 0, ?? '', ?? []). When 0 (or '', []) is a real value in the domain — numerical analysis, counts, ratios, aggregations — substituting it for absent data fabricates ground truth. Propagate null or throw; let the caller decide.
  • No "just in case" null checks layered on top of types/schemas that already guarantee shape.
  • No compat aliases, deprecated re-exports, or dual code paths for "old callers" — change the code.

If you think hardening is genuinely warranted, ask first with a one-line justification. Default is: do less.

Security

Gitleaks (Pre-Commit Hook)

  • Gitleaks runs as a pre-commit hook on all repos. Treat it as a hard gate, not advisory.
  • If gitleaks blocks a commit: investigate the finding, never bypass with --no-verify.
  • New repos should have .gitleaks.toml and a pre-commit hook (.husky/pre-commit or .git/hooks/pre-commit) installed.

Sensitive File Handling

  • Ask permission for sensitive files: Never read .env, .env.*, config files with secrets, or credential files without explicit user permission.
  • Respect privacy boundaries: Ask before accessing files that may contain API keys, passwords, or personal data.
  • Safe exploration: Use file listings and search tools to understand project structure before requesting access to sensitive files.

Common Vulnerabilities

  • Be careful not to introduce command injection, XSS, SQL injection, or other OWASP Top 10 vulnerabilities.
  • Validate untrusted input at I/O boundaries with schemas (see Schemas section).

Documentation and Code Comments

What TO Document

  • How things work: Logic, flow, and purpose of code
  • Why decisions were made: Architectural choices and trade-offs
  • Configuration details: Required settings, env vars, constants
  • Usage examples: How to use APIs, components, utilities
  • Security considerations: Auth, authorization, data protection
  • Performance optimizations: Caching, polling intervals, efficiency
  • Error handling: Error types, codes, recovery strategies
  • Type definitions: Complex types and their purposes
  • Business logic: Domain-specific rules and workflows

What NOT TO Document

  • Removal notes: Don't mention what was deleted, removed, or replaced
  • Historical changes: Don't reference previous implementations
  • Refactoring notes: Don't explain what used to exist
  • Cleanup comments: Don't note that something was "cleaned up" or "simplified"
  • Migration notes: Don't reference old patterns or deprecated methods

Examples

❌ Bad Comments

// Removed the old caching mechanism in favor of context state
// This used to call getLinkedPRsForIssue but now uses context
// Deleted the redundant API call
// No longer needed after refactoring

✅ Good Comments

// Fetches PR data from the context state which is updated by polling
// Uses JWT tokens for secure state management during OAuth flows
// Polls every 30 seconds to detect PR changes
// Returns error with code so caller can handle appropriately

Diagrams Over Prose

When explaining flow, architecture, or state machines, favor diagrams over multi-paragraph prose.

In persistent docs — use mermaid:

sequenceDiagram
    Client->>API: POST /login
    API->>DB: validate creds
    DB-->>API: user record
    API->>API: sign JWT
    API-->>Client: { token }
Loading

In conversation / terminal — use ASCII:

Client → API:    POST /login
API    → DB:     validate creds
DB     → API:    user record
API    → API:    sign JWT
API    → Client: { token }

Code Style

General Principles

  • Write code that explains what it does, not what it replaced.
  • Focus on current implementation details.
  • Document the "what" and "why" of the current solution.
  • Keep comments concise and relevant to present code.

Bespoke Scripts

  • Always use TypeScript with Bun (bun run script.ts), not Python.

Type Strictness

  • Define the most strict and restrictive type possible.
  • Avoid optional/nullable (?, | null, | undefined) unless absence is a valid, expected state:
    • Function parameters and return types
    • Class constructor parameters
    • Object properties and interfaces
    • Type definitions and DTOs

Imports

  • No re-exports: Don't re-export items from a file where the same thing is imported. Files should import directly from source.
  • Type Derivation: Derive types from schemas/source of truth rather than duplicating.

TypeScript LSP

Use LSP tool to understand code without reading entire files:

  • hover — get function/variable types
  • goToDefinition — find where symbols are defined
  • findReferences — find all usages
  • incomingCalls / outgoingCalls — trace call chains

Debugging

  • Read files before theorizing: Always read actual file contents to trace the code path and validate theories.
  • Evidence-based debugging: Do not propose theories about bugs without examining the relevant code files.
  • Follow the execution path: Trace through actual code rather than making assumptions.

Refactoring and Code Removal

  • Always search for dependencies: Before removing or refactoring, find all callers, imports, and references.
  • Don't assume visibility: Never assume you've seen all potential uses of a function, variable, or component.
  • Verify safe removal: Use grep/search tools to confirm code is truly unused before deleting.
  • Check indirect dependencies: Dynamic imports, string references, configuration-based usage.
  • When removing a file, always use the trash command, not rm. Ask before deleting files.

Functional Programming Patterns

Semi-functional — practical patterns, not dogma.

Core Principles:

  • Immutability first: Favor immutable data structures over mutations.
  • Dependency injection: Pass dependencies via function signatures, not global state.
  • Encapsulation: Make class members private/readonly wherever possible.
  • Pure functions: Prefer pure functions but don't sacrifice practicality.
  • Functional transformations: Use .filter/.map/.reduce over imperative loops with mutations.
  • Reducers: Use for complex state updates.
  • React: Avoid global mutable state. Use context or props.

Immutable Updates

// ❌ Mutation
function updateUser(user: User, changes: Partial<User>) {
  user.name = changes.name || user.name;
  return user;
}

// ✅ Immutable
function updateUser(user: User, changes: Partial<User>): User {
  return { ...user, ...changes };
}

Dependency Injection

// ❌ Global state
let globalConfig = { apiUrl: 'https://api.example.com' };
function fetchUser(id: string) {
  return fetch(`${globalConfig.apiUrl}/users/${id}`);
}

// ✅ Inject via signature
function fetchUser(id: string, config: { apiUrl: string }) {
  return fetch(`${config.apiUrl}/users/${id}`);
}

// ✅ Or factory for complex deps
function createUserService(config: { apiUrl: string }) {
  return {
    fetchUser: (id: string) => fetch(`${config.apiUrl}/users/${id}`),
    updateUser: (id: string, data: User) =>
      fetch(`${config.apiUrl}/users/${id}`, { method: 'PUT', body: JSON.stringify(data) })
  };
}

Class Design — Private + Readonly by Default

class UserRepository {
  private readonly db: Database;
  private readonly logger: Logger;

  constructor(db: Database, logger: Logger) {
    this.db = db;
    this.logger = logger;
  }

  async findById(id: string): Promise<User | null> {
    return this.db.query('SELECT * FROM users WHERE id = ?', [id]);
  }

  private validateUser(user: User): boolean {
    return user.email.includes('@');
  }
}

Asynchronous I/O Operations

  • Prefer async/await: Use async versions of file system and database operations.
  • Non-blocking: Avoid existsSync, mkdirSync, readFileSync.
  • Consistent patterns: Use async/await consistently across the codebase.
// ❌ Sync
import { existsSync, mkdirSync } from 'fs';
function ensureDir(path: string) {
  if (!existsSync(path)) mkdirSync(path, { recursive: true });
}

// ✅ Async
import { access, mkdir } from 'fs/promises';
async function ensureDir(path: string) {
  try { await access(path); }
  catch { await mkdir(path, { recursive: true }); }
}

Avoid Optional Chaining — Narrow Types with Schemas Instead

Optional chaining (?.) masks type uncertainty and defers error handling. Validate and narrow types upfront using schemas:

// ❌ Optional chaining hides type problems
function processUser(user: User | undefined) {
  const name = user?.profile?.name ?? 'Unknown';
  const email = user?.email?.toLowerCase();
  // If user is undefined, the error is hidden until later.
}

// ✅ Validate and narrow types explicitly
const UserSchema = z.object({
  profile: z.object({ name: z.string() }),
  email: z.string().email()
});

function processUser(rawUser: unknown) {
  const user = UserSchema.parse(rawUser); // throws if invalid — fail fast
  const name = user.profile.name;
  const email = user.email.toLowerCase();
}

Schemas and Type Safety at I/O Boundaries

Define rigorous schemas and parse data at all I/O boundaries.

API Response Validation

import { z } from 'zod';

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
  isActive: z.boolean(),
  createdAt: z.string().datetime()
});

type User = z.infer<typeof UserSchema>;

async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const rawData = await response.json();
  return UserSchema.parse(rawData);
}

File System I/O

const ConfigSchema = z.object({
  database: z.object({
    host: z.string(),
    port: z.number(),
    ssl: z.boolean().default(false)
  }),
  features: z.array(z.string())
});

type Config = z.infer<typeof ConfigSchema>;

async function loadConfig(filePath: string): Promise<Config> {
  const rawContent = await readFile(filePath, 'utf-8');
  return ConfigSchema.parse(JSON.parse(rawContent));
}

Environment Variables

const EnvSchema = z.object({
  DATABASE_URL: z.string().url(),
  API_KEY: z.string().min(1),
  PORT: z.coerce.number().default(3000),
  NODE_ENV: z.enum(['development', 'production', 'test'])
});

type Environment = z.infer<typeof EnvSchema>;
const env: Environment = EnvSchema.parse(process.env);

Architecture Documentation

  • Describe current system architecture.
  • Explain data flows and component interactions — prefer mermaid diagrams.
  • Document API contracts and interfaces.
  • Focus on how the system works now.

Example component diagram:

flowchart LR
    Client[Web Client] -->|REST| API[API Server]
    API -->|SQL| DB[(Postgres)]
    API -->|pub/sub| Queue[Redis Queue]
    Queue --> Worker[Worker]
    Worker -->|SQL| DB
Loading

UI/UX Principles

  • Mobile-first: Design for mobile screens first, enhance for larger.
  • Touch targets: Minimum 44x44px (h-11) for interactive elements.

Testing

  • Don't reference removed test cases.

Commit Messages

  • Keep commits small.
  • Describe what the commit adds or fixes.
  • Focus on the change's purpose and impact.
  • Use present tense ("Add JWT authentication" not "Removed direct token usage").

Pull Request Descriptions

  • Explain what the PR accomplishes in a simple, concist, bulleted list
  • Include any configuration changes needed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment