kaizen

Guide for continuous improvement, error proofing, and standardization. Use this skill when the user wants to improve code quality, refactor, or discuss process improvements.

View Source
name:kaizendescription:Guide for continuous improvement, error proofing, and standardization. Use this skill when the user wants to improve code quality, refactor, or discuss process improvements.

Kaizen: Continuous Improvement

Overview

Small improvements, continuously. Error-proof by design. Follow what works. Build only what's needed.

Core principle: Many small improvements beat one big change. Prevent errors at design time, not with fixes.

When to Use

Always applied for:

  • Code implementation and refactoring

  • Architecture and design decisions

  • Process and workflow improvements

  • Error handling and validation
  • Philosophy: Quality through incremental progress and prevention, not perfection through massive effort.

    The Four Pillars

    1. Continuous Improvement (Kaizen)

    Small, frequent improvements compound into major gains.

    Principles

    Incremental over revolutionary:

  • Make smallest viable change that improves quality

  • One improvement at a time

  • Verify each change before next

  • Build momentum through small wins
  • Always leave code better:

  • Fix small issues as you encounter them

  • Refactor while you work (within scope)

  • Update outdated comments

  • Remove dead code when you see it
  • Iterative refinement:

  • First version: make it work

  • Second pass: make it clear

  • Third pass: make it efficient

  • Don't try all three at once

  • // Iteration 1: Make it work
    const calculateTotal = (items: Item[]) => {
    let total = 0;
    for (let i = 0; i < items.length; i++) {
    total += items[i].price items[i].quantity;
    }
    return total;
    };

    // Iteration 2: Make it clear (refactor)
    const calculateTotal = (items: Item[]): number => {
    return items.reduce((total, item) => {
    return total + (item.price \
    item.quantity);
    }, 0);
    };

    // Iteration 3: Make it robust (add validation)
    const calculateTotal = (items: Item[]): number => {
    if (!items?.length) return 0;

    return items.reduce((total, item) => {
    if (item.price < 0 || item.quantity < 0) {
    throw new Error('Price and quantity must be non-negative');
    }
    return total + (item.price \ item.quantity);
    }, 0);
    };


    Each step is complete, tested, and working


    // Trying to do everything at once
    const calculateTotal = (items: Item[]): number => {
    // Validate, optimize, add features, handle edge cases all together
    if (!items?.length) return 0;
    const validItems = items.filter(item => {
    if (item.price < 0) throw new Error('Negative price');
    if (item.quantity < 0) throw new Error('Negative quantity');
    return item.quantity > 0; // Also filtering zero quantities
    });
    // Plus caching, plus logging, plus currency conversion...
    return validItems.reduce(...); // Too many concerns at once
    };

    Overwhelming, error-prone, hard to verify

    In Practice

    When implementing features:

  • Start with simplest version that works

  • Add one improvement (error handling, validation, etc.)

  • Test and verify

  • Repeat if time permits

  • Don't try to make it perfect immediately
  • When refactoring:

  • Fix one smell at a time

  • Commit after each improvement

  • Keep tests passing throughout

  • Stop when "good enough" (diminishing returns)
  • When reviewing code:

  • Suggest incremental improvements (not rewrites)

  • Prioritize: critical → important → nice-to-have

  • Focus on highest-impact changes first

  • Accept "better than before" even if not perfect
  • 2. Poka-Yoke (Error Proofing)

    Design systems that prevent errors at compile/design time, not runtime.

    Principles

    Make errors impossible:

  • Type system catches mistakes

  • Compiler enforces contracts

  • Invalid states unrepresentable

  • Errors caught early (left of production)
  • Design for safety:

  • Fail fast and loudly

  • Provide helpful error messages

  • Make correct path obvious

  • Make incorrect path difficult
  • Defense in layers:

  • Type system (compile time)

  • Validation (runtime, early)

  • Guards (preconditions)

  • Error boundaries (graceful degradation)
  • Type System Error Proofing


    // Error: string status can be any value
    type OrderBad = {
    status: string; // Can be "pending", "PENDING", "pnding", anything!
    total: number;
    };

    // Good: Only valid states possible
    type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered';
    type Order = {
    status: OrderStatus;
    total: number;
    };

    // Better: States with associated data
    type Order =
    | { status: 'pending'; createdAt: Date }
    | { status: 'processing'; startedAt: Date; estimatedCompletion: Date }
    | { status: 'shipped'; trackingNumber: string; shippedAt: Date }
    | { status: 'delivered'; deliveredAt: Date; signature: string };

    // Now impossible to have shipped without trackingNumber


    Type system prevents entire classes of errors


    // Make invalid states unrepresentable
    type NonEmptyArray<T> = [T, ...T[]];

    const firstItem = <T>(items: NonEmptyArray<T>): T => {
    return items[0]; // Always safe, never undefined!
    };

    // Caller must prove array is non-empty
    const items: number[] = [1, 2, 3];
    if (items.length > 0) {
    firstItem(items as NonEmptyArray<number>); // Safe
    }

    Function signature guarantees safety

    Validation Error Proofing


    // Error: Validation after use
    const processPayment = (amount: number) => {
    const fee = amount
    0.03; // Used before validation!
    if (amount <= 0) throw new Error('Invalid amount');
    // ...
    };

    // Good: Validate immediately
    const processPayment = (amount: number) => {
    if (amount <= 0) {
    throw new Error('Payment amount must be positive');
    }
    if (amount > 10000) {
    throw new Error('Payment exceeds maximum allowed');
    }

    const fee = amount \ 0.03;
    // ... now safe to use
    };

    // Better: Validation at boundary with branded type
    type PositiveNumber = number & { readonly \_\_brand: 'PositiveNumber' };

    const validatePositive = (n: number): PositiveNumber => {
    if (n <= 0) throw new Error('Must be positive');
    return n as PositiveNumber;
    };

    const processPayment = (amount: PositiveNumber) => {
    // amount is guaranteed positive, no need to check
    const fee = amount \
    0.03;
    };

    // Validate at system boundary
    const handlePaymentRequest = (req: Request) => {
    const amount = validatePositive(req.body.amount); // Validate once
    processPayment(amount); // Use everywhere safely
    };


    Validate once at boundary, safe everywhere else

    Guards and Preconditions


    // Early returns prevent deeply nested code
    const processUser = (user: User | null) => {
    if (!user) {
    logger.error('User not found');
    return;
    }

    if (!user.email) {
    logger.error('User email missing');
    return;
    }

    if (!user.isActive) {
    logger.info('User inactive, skipping');
    return;
    }

    // Main logic here, guaranteed user is valid and active
    sendEmail(user.email, 'Welcome!');
    };

    Guards make assumptions explicit and enforced

    Configuration Error Proofing


    // Error: Optional config with unsafe defaults
    type ConfigBad = {
    apiKey?: string;
    timeout?: number;
    };

    const client = new APIClient({ timeout: 5000 }); // apiKey missing!

    // Good: Required config, fails early
    type Config = {
    apiKey: string;
    timeout: number;
    };

    const loadConfig = (): Config => {
    const apiKey = process.env.API_KEY;
    if (!apiKey) {
    throw new Error('API_KEY environment variable required');
    }

    return {
    apiKey,
    timeout: 5000,
    };
    };

    // App fails at startup if config invalid, not during request
    const config = loadConfig();
    const client = new APIClient(config);


    Fail at startup, not in production

    In Practice

    When designing APIs:

  • Use types to constrain inputs

  • Make invalid states unrepresentable

  • Return Result instead of throwing

  • Document preconditions in types
  • When handling errors:

  • Validate at system boundaries
  • Use guards for preconditions

  • Fail fast with clear messages

  • Log context for debugging
  • When configuring:

  • Required over optional with defaults

  • Validate all config at startup

  • Fail deployment if config invalid

  • Don't allow partial configurations
  • 3. Standardized Work


    Follow established patterns. Document what works. Make good practices easy to follow.

    Principles

    Consistency over cleverness:

  • Follow existing codebase patterns

  • Don't reinvent solved problems

  • New pattern only if significantly better

  • Team agreement on new patterns
  • Documentation lives with code:

  • README for setup and architecture

  • CLAUDE.md for AI coding conventions

  • Comments for "why", not "what"

  • Examples for complex patterns
  • Automate standards:

  • Linters enforce style

  • Type checks enforce contracts

  • Tests verify behavior

  • CI/CD enforces quality gates
  • Following Patterns


    // Existing codebase pattern for API clients
    class UserAPIClient {
    async getUser(id: string): Promise<User> {
    return this.fetch(
    /users/${id});
    }
    }

    // New code follows the same pattern
    class OrderAPIClient {
    async getOrder(id: string): Promise<Order> {
    return this.fetch(
    /orders/${id});
    }
    }

    Consistency makes codebase predictable


    // Existing pattern uses classes
    class UserAPIClient { / ... / }

    // New code introduces different pattern without discussion
    const getOrder = async (id: string): Promise<Order> => {
    // Breaking consistency "because I prefer functions"
    };


    Inconsistency creates confusion

    Error Handling Patterns


    // Project standard: Result type for recoverable errors
    type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

    // All services follow this pattern
    const fetchUser = async (id: string): Promise<Result<User, Error>> => {
    try {
    const user = await db.users.findById(id);
    if (!user) {
    return { ok: false, error: new Error('User not found') };
    }
    return { ok: true, value: user };
    } catch (err) {
    return { ok: false, error: err as Error };
    }
    };

    // Callers use consistent pattern
    const result = await fetchUser('123');
    if (!result.ok) {
    logger.error('Failed to fetch user', result.error);
    return;
    }
    const user = result.value; // Type-safe!

    Standard pattern across codebase

    Documentation Standards


    /*
    Retries an async operation with exponential backoff.

    Why: Network requests fail temporarily; retrying improves reliability
    When to use: External API calls, database operations
    When not to use: User input validation, internal function calls

    @example
    const result = await retry(
    () => fetch('https://api.example.com/data'),
    { maxAttempts: 3, baseDelay: 1000 }
    );
    /
    const retry = async <T>(
    operation: () => Promise<T>,
    options: RetryOptions
    ): Promise<T> => {
    // Implementation...
    };

    Documents why, when, and how

    In Practice

    Before adding new patterns:

  • Search codebase for similar problems solved

  • Check CLAUDE.md for project conventions

  • Discuss with team if breaking from pattern

  • Update docs when introducing new pattern
  • When writing code:

  • Match existing file structure

  • Use same naming conventions

  • Follow same error handling approach

  • Import from same locations
  • When reviewing:

  • Check consistency with existing code

  • Point to examples in codebase

  • Suggest aligning with standards

  • Update CLAUDE.md if new standard emerges
  • 4. Just-In-Time (JIT)

    Build what's needed now. No more, no less. Avoid premature optimization and over-engineering.

    Principles

    YAGNI (You Aren't Gonna Need It):

  • Implement only current requirements

  • No "just in case" features

  • No "we might need this later" code

  • Delete speculation
  • Simplest thing that works:

  • Start with straightforward solution

  • Add complexity only when needed

  • Refactor when requirements change

  • Don't anticipate future needs
  • Optimize when measured:

  • No premature optimization

  • Profile before optimizing

  • Measure impact of changes

  • Accept "good enough" performance
  • YAGNI in Action


    // Current requirement: Log errors to console
    const logError = (error: Error) => {
    console.error(error.message);
    };

    Simple, meets current need


    // Over-engineered for "future needs"
    interface LogTransport {
    write(level: LogLevel, message: string, meta?: LogMetadata): Promise<void>;
    }

    class ConsoleTransport implements LogTransport { /_... _/ }
    class FileTransport implements LogTransport { /_ ... _/ }
    class RemoteTransport implements LogTransport { /_ ..._/ }

    class Logger {
    private transports: LogTransport[] = [];
    private queue: LogEntry[] = [];
    private rateLimiter: RateLimiter;
    private formatter: LogFormatter;

    // 200 lines of code for "maybe we'll need it"
    }

    const logError = (error: Error) => {
    Logger.getInstance().log('error', error.message);
    };


    Building for imaginary future requirements

    When to add complexity:

  • Current requirement demands it

  • Pain points identified through use

  • Measured performance issues

  • Multiple use cases emerged

  • // Start simple
    const formatCurrency = (amount: number): string => {
    return
    $${amount.toFixed(2)};
    };

    // Requirement evolves: support multiple currencies
    const formatCurrency = (amount: number, currency: string): string => {
    const symbols = { USD: '$', EUR: '€', GBP: '£' };
    return
    ${symbols[currency]}${amount.toFixed(2)};
    };

    // Requirement evolves: support localization
    const formatCurrency = (amount: number, locale: string): string => {
    return new Intl.NumberFormat(locale, {\n style: 'currency',
    currency: locale === 'en-US' ? 'USD' : 'EUR',
    }).format(amount);
    };

    Complexity added only when needed

    Premature Abstraction


    // One use case, but building generic framework
    abstract class BaseCRUDService<T> {
    abstract getAll(): Promise<T[]>;
    abstract getById(id: string): Promise<T>;
    abstract create(data: Partial<T>): Promise<T>;
    abstract update(id: string, data: Partial<T>): Promise<T>;
    abstract delete(id: string): Promise<void>;
    }

    class GenericRepository<T> { /_300 lines _/ }
    class QueryBuilder<T> { /_ 200 lines_/ }
    // ... building entire ORM for single table


    Massive abstraction for uncertain future


    // Simple functions for current needs
    const getUsers = async (): Promise<User[]> => {
    return db.query('SELECT
    FROM users');
    };

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

    // When pattern emerges across multiple entities, then abstract

    Abstract only when pattern proven across 3+ cases

    Performance Optimization


    // Current: Simple approach
    const filterActiveUsers = (users: User[]): User[] => {
    return users.filter(user => user.isActive);
    };

    // Benchmark shows: 50ms for 1000 users (acceptable)
    // ✓ Ship it, no optimization needed

    // Later: After profiling shows this is bottleneck
    // Then optimize with indexed lookup or caching


    Optimize based on measurement, not assumptions


    // Premature optimization
    const filterActiveUsers = (users: User[]): User[] => {
    // "This might be slow, so let's cache and index"
    const cache = new WeakMap();
    const indexed = buildBTreeIndex(users, 'isActive');
    // 100 lines of optimization code
    // Adds complexity, harder to maintain
    // No evidence it was needed
    };\

    Complex solution for unmeasured problem

    In Practice

    When implementing:

  • Solve the immediate problem

  • Use straightforward approach

  • Resist "what if" thinking

  • Delete speculative code
  • When optimizing:

  • Profile first, optimize second

  • Measure before and after

  • Document why optimization needed

  • Keep simple version in tests
  • When abstracting:

  • Wait for 3+ similar cases (Rule of Three)

  • Make abstraction as simple as possible

  • Prefer duplication over wrong abstraction

  • Refactor when pattern clear
  • Integration with Commands

    The Kaizen skill guides how you work. The commands provide structured analysis:

  • /why: Root cause analysis (5 Whys)

  • /cause-and-effect: Multi-factor analysis (Fishbone)

  • /plan-do-check-act: Iterative improvement cycles

  • /analyse-problem: Comprehensive documentation (A3)

  • /analyse: Smart method selection (Gemba/VSM/Muda)
  • Use commands for structured problem-solving. Apply skill for day-to-day development.

    Red Flags

    Violating Continuous Improvement:

  • "I'll refactor it later" (never happens)

  • Leaving code worse than you found it

  • Big bang rewrites instead of incremental
  • Violating Poka-Yoke:

  • "Users should just be careful"

  • Validation after use instead of before

  • Optional config with no validation
  • Violating Standardized Work:

  • "I prefer to do it my way"

  • Not checking existing patterns

  • Ignoring project conventions
  • Violating Just-In-Time:

  • "We might need this someday"

  • Building frameworks before using them

  • Optimizing without measuring
  • Remember

    Kaizen is about:

  • Small improvements continuously

  • Preventing errors by design

  • Following proven patterns

  • Building only what's needed
  • Not about:

  • Perfection on first try

  • Massive refactoring projects

  • Clever abstractions

  • Premature optimization
  • Mindset: Good enough today, better tomorrow. Repeat.