Claude Code Plugins

Community-maintained marketplace

Feedback

tzurot-constants

@lbds137/tzurot
7
0

Constants management for Tzurot v3 - Identifies magic numbers/strings, guides domain-separated organization, and enforces centralization patterns. Use when writing code with hardcoded values or refactoring.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name tzurot-constants
description Constants management for Tzurot v3 - Identifies magic numbers/strings, guides domain-separated organization, and enforces centralization patterns. Use when writing code with hardcoded values or refactoring.
lastUpdated 2025-11-19

Tzurot v3 Constants Management

Use this skill when: Writing new code with timeouts/delays/strings, refactoring to remove magic numbers, or adding new constants to common-types.

Core Principle

NO MAGIC NUMBERS OR STRINGS - Use named constants from @tzurot/common-types/constants

Magic values make code hard to maintain, search, and update. Constants provide:

  • Discoverability - Easy to find all uses of a value
  • Consistency - Same value used everywhere
  • Documentation - JSDoc explains WHY the value was chosen
  • Type Safety - TypeScript ensures correct usage

Constants Location

All constants live in packages/common-types/src/constants/ organized by domain:

packages/common-types/src/constants/
├── index.ts          # Barrel export
├── ai.ts             # AI provider settings, model defaults
├── timing.ts         # Timeouts, intervals, TTLs, retry config
├── queue.ts          # Queue config, job prefixes, Redis keys
├── discord.ts        # Discord API limits, colors, text limits
├── error.ts          # Error codes, error messages
├── media.ts          # Media limits, content types
├── message.ts        # Message roles, placeholders
└── service.ts        # Service defaults, app settings

When to Create Constants

✅ ALWAYS Create Constants For:

1. Timeouts and Delays

// ❌ BAD - Magic number
await new Promise(resolve => setTimeout(resolve, 5 * 60 * 1000));

// ✅ GOOD - Named constant
import { TIMEOUTS } from '@tzurot/common-types';
await new Promise(resolve => setTimeout(resolve, TIMEOUTS.CACHE_TTL));

2. Redis Key Prefixes

// ❌ BAD - Hardcoded string
await redis.set(`webhook:${messageId}`, data);

// ✅ GOOD - Constant prefix
import { REDIS_KEY_PREFIXES } from '@tzurot/common-types';
await redis.set(`${REDIS_KEY_PREFIXES.WEBHOOK_MESSAGE}${messageId}`, data);

3. Retry Counts and Intervals

// ❌ BAD - Magic numbers
const maxRetries = 3;
const retryDelay = 1000;

// ✅ GOOD - Retry configuration
import { RETRY_CONFIG } from '@tzurot/common-types';
const maxRetries = RETRY_CONFIG.MAX_ATTEMPTS;
const retryDelay = RETRY_CONFIG.INITIAL_DELAY_MS;

4. Buffer Sizes and Limits

// ❌ BAD - Magic number
if (message.content.length > 2000) {
  // chunk message
}

// ✅ GOOD - Discord limit constant
import { TEXT_LIMITS } from '@tzurot/common-types';
if (message.content.length > TEXT_LIMITS.MESSAGE_MAX_LENGTH) {
  // chunk message
}

5. Repeated String Literals

// ❌ BAD - Repeated strings
logger.info('[Bot] Starting...');
logger.info('[Bot] Ready!');

// ✅ GOOD - Constant prefix
const LOG_PREFIX = '[Bot]';
logger.info(`${LOG_PREFIX} Starting...`);
logger.info(`${LOG_PREFIX} Ready!`);

6. Event Types and Channel Names

// ❌ BAD - String literals
client.on('messageCreate', handler);
client.on('interactionCreate', handler);

// ✅ GOOD - Constants (if repeated multiple times)
const DISCORD_EVENTS = {
  MESSAGE_CREATE: 'messageCreate',
  INTERACTION_CREATE: 'interactionCreate',
} as const;
client.on(DISCORD_EVENTS.MESSAGE_CREATE, handler);

❌ DON'T Create Constants For:

1. One-Off String Literals

// ✅ FINE - Used once, clear in context
logger.info('Personality cache initialized');

2. Simple Struct Length Checks

// ✅ FINE - Structure validation, not a "magic number"
if (Object.keys(event).length === 2) {
  // Valid event structure
}

3. Test-Only Values

// ✅ FINE - Test-specific, not shared
const mockUserId = 'test-user-123';

4. Self-Documenting Values

// ✅ FINE - The value IS the documentation
const defaultTemperature = 0.8; // AI temperature parameter

Domain-Separated Constants

ai.ts - AI Provider Settings

export const AI_DEFAULTS = {
  /** Default temperature for AI responses (0.0 = deterministic, 1.0 = creative) */
  TEMPERATURE: 0.8,
  /** Maximum tokens for AI responses */
  MAX_TOKENS: 4096,
  /** Timeout for AI API calls (milliseconds) */
  API_TIMEOUT: 60000,
} as const;

export const MODEL_DEFAULTS = {
  /** Default OpenRouter model for personalities without specific config */
  DEFAULT: 'anthropic/claude-sonnet-4.5',
  /** Fallback model if primary fails */
  FALLBACK: 'anthropic/claude-sonnet-3.5',
} as const;

timing.ts - Timeouts, Intervals, TTLs

export const TIMEOUTS = {
  /** Cache TTL for personality/user data (5 minutes) */
  CACHE_TTL: 5 * 60 * 1000,
  /** LLM invocation timeout (8 minutes total) */
  LLM_INVOCATION: 480000,
  /** Job wait timeout in gateway (10 minutes) */
  JOB_WAIT: 600000,
} as const;

export const INTERVALS = {
  /** Webhook cache cleanup interval (1 minute) */
  WEBHOOK_CLEANUP: 60000,
  /** Typing indicator refresh interval (8 seconds) */
  TYPING_INDICATOR_REFRESH: 8000,
  /** Webhook message tracking TTL in Redis (7 days in seconds) */
  WEBHOOK_MESSAGE_TTL: 7 * 24 * 60 * 60,
} as const;

export const RETRY_CONFIG = {
  /** Standard retry attempts for ALL components */
  MAX_ATTEMPTS: 3,
  /** Initial delay before first retry (1 second) */
  INITIAL_DELAY_MS: 1000,
  /** Maximum delay between retries (10 seconds) */
  MAX_DELAY_MS: 10000,
} as const;

queue.ts - Queue Configuration, Redis Keys

export const QUEUE_CONFIG = {
  /** Maximum number of completed jobs to keep */
  COMPLETED_HISTORY_LIMIT: 10,
  /** Maximum number of failed jobs to keep */
  FAILED_HISTORY_LIMIT: 50,
} as const;

export const REDIS_KEY_PREFIXES = {
  /** Prefix for job result storage */
  JOB_RESULT: 'job-result:',
  /** Prefix for webhook message -> personality mapping */
  WEBHOOK_MESSAGE: 'webhook:',
  /** Prefix for voice transcript cache */
  VOICE_TRANSCRIPT: 'transcript:',
} as const;

export const REDIS_CHANNELS = {
  /** Channel for cache invalidation events */
  CACHE_INVALIDATION: 'cache:invalidation',
} as const;

discord.ts - Discord API Limits

export const TEXT_LIMITS = {
  /** Discord message content limit */
  MESSAGE_MAX_LENGTH: 2000,
  /** Discord embed title limit */
  EMBED_TITLE_MAX_LENGTH: 256,
  /** Discord embed description limit */
  EMBED_DESCRIPTION_MAX_LENGTH: 4096,
} as const;

export const DISCORD_LIMITS = {
  /** Maximum embeds per message */
  MAX_EMBEDS_PER_MESSAGE: 10,
  /** Maximum fields per embed */
  MAX_FIELDS_PER_EMBED: 25,
  /** Rate limit: messages per channel per second */
  MESSAGES_PER_CHANNEL_PER_SECOND: 5,
} as const;

error.ts - Error Codes and Messages

export enum TransientErrorCode {
  NetworkError = 'NETWORK_ERROR',
  RateLimitError = 'RATE_LIMIT_ERROR',
  TimeoutError = 'TIMEOUT_ERROR',
  ServiceUnavailable = 'SERVICE_UNAVAILABLE',
}

export const ERROR_MESSAGES = {
  [TransientErrorCode.NetworkError]: 'Network connection failed',
  [TransientErrorCode.RateLimitError]: 'Rate limit exceeded',
  [TransientErrorCode.TimeoutError]: 'Operation timed out',
  [TransientErrorCode.ServiceUnavailable]: 'Service temporarily unavailable',
} as const;

Import Pattern

Always import from the barrel export:

// ✅ CORRECT - Import from index
import {
  TIMEOUTS,
  INTERVALS,
  REDIS_KEY_PREFIXES,
  REDIS_CHANNELS,
  TEXT_LIMITS,
  RETRY_CONFIG,
} from '@tzurot/common-types';

// ❌ WRONG - Direct file imports
import { TIMEOUTS } from '@tzurot/common-types/constants/timing';

Naming Conventions

Constant Objects: SCREAMING_SNAKE_CASE

export const RETRY_CONFIG = {
  /* ... */
} as const;
export const REDIS_KEY_PREFIXES = {
  /* ... */
} as const;

Properties: SCREAMING_SNAKE_CASE

export const TIMEOUTS = {
  CACHE_TTL: 5 * 60 * 1000,
  LLM_INVOCATION: 480000,
} as const;

Always Use as const

// ✅ GOOD - Readonly, TypeScript infers literal types
export const LIMITS = {
  MAX_RETRIES: 3,
} as const;

// ❌ BAD - Mutable, TypeScript infers wide types
export const LIMITS = {
  MAX_RETRIES: 3,
};

Documentation Requirements

Always add JSDoc comments explaining:

  1. What the constant represents
  2. Why the value was chosen
  3. Units (seconds, milliseconds, etc.)
export const TIMEOUTS = {
  /**
   * Cache TTL for personality/user data (5 minutes)
   *
   * Chosen to balance:
   * - Fresh data for config changes (users notice within 5min)
   * - Reduced database load (most requests hit cache)
   * - Memory usage (TTL prevents indefinite growth)
   */
  CACHE_TTL: 5 * 60 * 1000,

  /**
   * LLM invocation timeout for all retry attempts combined (8 minutes)
   *
   * Calculation:
   * - Per-attempt timeout: 3 minutes (LLM_PER_ATTEMPT)
   * - Max attempts: 3 (RETRY_CONFIG.MAX_ATTEMPTS)
   * - Total: 3 * 3min = 9min (rounded down to 8min for safety buffer)
   */
  LLM_INVOCATION: 480000,
} as const;

Config vs. Constants

Environment Variables (Config) - Values that differ per environment:

  • API keys (DISCORD_TOKEN, OPENROUTER_API_KEY)
  • Database URLs (DATABASE_URL, REDIS_URL)
  • Service URLs (GATEWAY_URL, PUBLIC_GATEWAY_URL)

Application Constants - Values that are the same across environments:

  • Retry limits (RETRY_CONFIG.MAX_ATTEMPTS)
  • Timeouts (TIMEOUTS.CACHE_TTL)
  • Discord limits (TEXT_LIMITS.MESSAGE_MAX_LENGTH)
// ❌ WRONG - Hardcoded API key
const apiKey = 'sk-1234567890';

// ✅ RIGHT - From environment
const apiKey = process.env.OPENROUTER_API_KEY;

// ✅ RIGHT - Application constant
const maxRetries = RETRY_CONFIG.MAX_ATTEMPTS;

Centralization Rule

If a value is used in more than one file, it moves to common-types/constants

// services/bot-client/src/redis.ts
const WEBHOOK_TTL = 7 * 24 * 60 * 60; // Used here

// services/api-gateway/src/webhooks.ts
const WEBHOOK_TTL = 7 * 24 * 60 * 60; // AND here

// ❌ DUPLICATION - Move to common-types!
// packages/common-types/src/constants/timing.ts
export const INTERVALS = {
  WEBHOOK_MESSAGE_TTL: 7 * 24 * 60 * 60,
} as const;

// ✅ CENTRALIZED - Both services import this

Refactoring to Constants

Step 1: Identify Magic Values

# Search for common patterns
grep -r "60000\|5000\|3000" src/
grep -r "redis.set\|redis.get" src/
grep -r "setTimeout\|setInterval" src/

Step 2: Determine Domain

  • Timeouts/delays → timing.ts
  • Redis keys → queue.ts
  • Discord limits → discord.ts
  • AI config → ai.ts

Step 3: Add to Appropriate File

// packages/common-types/src/constants/timing.ts
export const TIMEOUTS = {
  /** New constant with documentation */
  MY_NEW_TIMEOUT: 60000,
} as const;

Step 4: Export from Index

// packages/common-types/src/constants/index.ts
export { TIMEOUTS } from './timing.js';

Step 5: Update Usage

// Before
const timeout = 60000;

// After
import { TIMEOUTS } from '@tzurot/common-types';
const timeout = TIMEOUTS.MY_NEW_TIMEOUT;

Anti-Patterns

❌ Don't Nest Constants Objects Too Deeply

// ❌ BAD - Too nested
export const CONFIG = {
  AI: {
    PROVIDERS: {
      OPENROUTER: {
        TIMEOUTS: {
          DEFAULT: 60000,
        },
      },
    },
  },
};

// ✅ GOOD - Flat structure
export const AI_TIMEOUTS = {
  OPENROUTER_DEFAULT: 60000,
} as const;

❌ Don't Mix Constants and Functions

// ❌ BAD - Functions don't belong in constants files
export const HELPERS = {
  MAX_RETRIES: 3,
  calculateDelay: (attempt: number) => attempt * 1000,
};

// ✅ GOOD - Constants only, put functions in utils/
export const RETRY_CONFIG = {
  MAX_RETRIES: 3,
  INITIAL_DELAY_MS: 1000,
} as const;

// utils/retry.ts
export function calculateDelay(attempt: number): number {
  return attempt * RETRY_CONFIG.INITIAL_DELAY_MS;
}

❌ Don't Use String Enums for Constants

// ❌ BAD - String enum is overkill
enum RedisKeys {
  Webhook = 'webhook:',
  Transcript = 'transcript:',
}

// ✅ GOOD - Simple const object
export const REDIS_KEY_PREFIXES = {
  WEBHOOK_MESSAGE: 'webhook:',
  VOICE_TRANSCRIPT: 'transcript:',
} as const;

Related Skills

  • tzurot-testing - Use constants for test data and timeouts
  • tzurot-async-flow - Job naming conventions use constants
  • tzurot-observability - Logging constants and error codes
  • tzurot-shared-types - Export constants from common-types

References

  • Constants documentation: CLAUDE.md#constants-and-magic-numbers
  • Existing constants: packages/common-types/src/constants/