pedantic-coder/skills/magic-value-elimination/SKILL.md
This skill should be used when the user's code contains inline string literals, unexplained numbers, hardcoded timeout values, status strings, or any value that should be a named constant or enum. Covers the elimination of magic values and the rule that every literal should have a name.
npx skillsauth add oborchers/fractional-cto magic-value-eliminationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
If a value appears in code without a name explaining what it means, it is a magic value. Magic values are not "minor style issues." They are comprehension failures. When a reader sees 86400, they must stop, count zeros, divide by 60 twice, and conclude "oh, seconds per day." When they see SECONDS_PER_DAY, they keep reading. That interruption -- multiplied by every developer, every review, every debugging session -- is the cost of a magic value.
Eliminate them. All of them.
If a value appears in code without a name explaining what it means, it is a magic value and it must be eliminated.
No exceptions for "obvious" values. No exceptions for "it is only used once." No exceptions for "everyone knows what 200 means." Name it. The name is documentation that never goes stale, a grep target that always works, and a single point of change when the value evolves.
Every number that is not 0, 1, or -1 in an obvious context needs a name.
GOOD:
SECONDS_PER_DAY = 86400
MAX_BATCH_SIZE = 1000
RELEVANCE_THRESHOLD = 0.5
MAX_RETRY_ATTEMPTS = 3
DEFAULT_PAGE_SIZE = 20
BCRYPT_ROUNDS = 12
HTTP_TIMEOUT_SECONDS = 30
CACHE_TTL_MINUTES = 15
MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024 # 10 MB
BAD:
if elapsed > 86400:
expire_session()
results = results[:1000]
if score < 0.5:
discard(item)
for attempt in range(3):
try:
response = client.post(url, timeout=30)
break
except Timeout:
time.sleep(2 ** attempt)
The reader of the bad code must reverse-engineer the meaning of every number. 86400 -- is that seconds? Milliseconds? Why that specific value? 1000 -- is that a page size? A rate limit? An arbitrary cap? 0.5 -- half of what? 3 -- why three? 30 -- thirty what?
String literals that represent categories, statuses, types, roles, or any value from a finite set must be enums or named constants. No inline strings.
GOOD -- Python:
from enum import StrEnum
class OrderStatus(StrEnum):
PENDING = "pending"
PROCESSING = "processing"
SHIPPED = "shipped"
DELIVERED = "delivered"
CANCELLED = "cancelled"
class UserRole(StrEnum):
ADMIN = "admin"
MEMBER = "member"
VIEWER = "viewer"
CONTENT_TYPE_JSON = "application/json"
CONTENT_TYPE_FORM = "application/x-www-form-urlencoded"
AUTH_HEADER = "Authorization"
BEARER_PREFIX = "Bearer "
BAD -- Python:
if order.status == "pending":
order.status = "processing"
if user.role == "admin":
allow_action()
headers = {"Content-Type": "application/json"}
headers["Authorization"] = "Bearer " + token
"pending", "processing", "admin", "application/json", "Bearer " -- every one of these is a typo waiting to happen. "penidng" compiles. OrderStatus.PENIDNG does not.
GOOD -- TypeScript:
const ORDER_STATUS = {
PENDING: "pending",
PROCESSING: "processing",
SHIPPED: "shipped",
DELIVERED: "delivered",
CANCELLED: "cancelled",
} as const;
type OrderStatus = (typeof ORDER_STATUS)[keyof typeof ORDER_STATUS];
const USER_ROLE = {
ADMIN: "admin",
MEMBER: "member",
VIEWER: "viewer",
} as const;
type UserRole = (typeof USER_ROLE)[keyof typeof USER_ROLE];
const CONTENT_TYPE_JSON = "application/json";
const AUTH_HEADER = "Authorization";
BAD -- TypeScript:
if (order.status === "pending") {
order.status = "processing";
}
if (user.role === "admin") {
allowAction();
}
const headers = { "Content-Type": "application/json" };
GOOD -- Go:
type OrderStatus string
const (
OrderStatusPending OrderStatus = "pending"
OrderStatusProcessing OrderStatus = "processing"
OrderStatusShipped OrderStatus = "shipped"
OrderStatusDelivered OrderStatus = "delivered"
OrderStatusCancelled OrderStatus = "cancelled"
)
type UserRole string
const (
UserRoleAdmin UserRole = "admin"
UserRoleMember UserRole = "member"
UserRoleViewer UserRole = "viewer"
)
const (
ContentTypeJSON = "application/json"
AuthHeader = "Authorization"
BearerPrefix = "Bearer "
)
BAD -- Go:
if order.Status == "pending" {
order.Status = "processing"
}
if user.Role == "admin" {
allowAction()
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer " + token)
A boolean parameter whose meaning is not self-evident from the call site must be replaced with a named parameter or an enum.
BAD:
process_order(order, True) # True what? Skip validation? Rush shipping?
send_email(user, False, True) # False what? True what?
create_report(data, True, False) # Impossible to read at the call site
GOOD:
process_order(order, skip_validation=True)
send_email(user, include_attachments=False, track_opens=True)
create_report(data, include_charts=True, send_immediately=False)
In TypeScript, use options objects instead of boolean parameters:
BAD:
processOrder(order, true, false);
GOOD:
processOrder(order, { skipValidation: true, rushShipping: false });
In Go, use option types or explicit parameters:
BAD:
ProcessOrder(order, true, false)
GOOD:
ProcessOrder(order, ProcessOptions{SkipValidation: true, RushShipping: false})
Constants must live where they are used. Scattering them randomly makes them impossible to find. Centralizing everything into one massive constants.go makes them impossible to maintain. The rule:
MAX_BATCH_SIZE is only used in batch_processor.py, it lives in batch_processor.py.constants.py, constants.ts, constants.go) when used across two or more modules.types.py, types.ts), even if only used in one module. Enums define domain concepts, not implementation details.project/
constants.py # Shared: SECONDS_PER_DAY, MAX_FILE_SIZE_BYTES, CONTENT_TYPE_JSON
types.py # Enums: OrderStatus, UserRole, PaymentMethod
batch_processor.py # Module-specific: MAX_BATCH_SIZE (used only here)
cache.py # Module-specific: DEFAULT_TTL_SECONDS (used only here)
For every finite set of values, use a string enum. Not a list of string constants. Not inline strings. A string enum gives you:
OrderStatus.PENIDNG is a typo that fails at compile time; "penidng" is a typo that fails in productionPython: StrEnum (Python 3.11+)
from enum import StrEnum
class PaymentMethod(StrEnum):
CREDIT_CARD = "credit_card"
DEBIT_CARD = "debit_card"
BANK_TRANSFER = "bank_transfer"
CRYPTO = "crypto"
# Serializes to string automatically:
# json.dumps({"method": PaymentMethod.CREDIT_CARD})
# -> '{"method": "credit_card"}'
TypeScript: as const with derived type
const PAYMENT_METHOD = {
CREDIT_CARD: "credit_card",
DEBIT_CARD: "debit_card",
BANK_TRANSFER: "bank_transfer",
CRYPTO: "crypto",
} as const;
type PaymentMethod = (typeof PAYMENT_METHOD)[keyof typeof PAYMENT_METHOD];
// "credit_card" | "debit_card" | "bank_transfer" | "crypto"
Go: typed string constants
type PaymentMethod string
const (
PaymentMethodCreditCard PaymentMethod = "credit_card"
PaymentMethodDebitCard PaymentMethod = "debit_card"
PaymentMethodBankTransfer PaymentMethod = "bank_transfer"
PaymentMethodCrypto PaymentMethod = "crypto"
)
timeout=30 -- thirty what? timeout=HTTP_TIMEOUT_SECONDS -- clear.range(len(items)) -- len(items) is obvious. users = [] -- empty list is obvious.These values can appear inline when their meaning is obvious from immediate context:
0 -- loop counter initialization, empty count, origin index1 -- increment, single item, next index-1 -- sentinel "not found" return, decrement"" -- empty string initializationTrue / False -- boolean return, obvious flag assignmentNone / null / nil -- absence of valueEven these have limits. sleep(1) -- one what? Second? Millisecond? Name it: RETRY_DELAY_SECONDS = 1.
The "obvious number" excuse. "Everyone knows 200 is HTTP OK." Does everyone know 429 is rate-limited? Does everyone know 507 is insufficient storage? Name them all or name none. HTTP_STATUS_OK = 200 costs nothing and helps everyone.
The "only used once" excuse. A magic value used once is still a magic value. if retries > 3 -- why 3? What changes if the threshold moves to 5? With MAX_RETRY_ATTEMPTS = 3, the answer is obvious and the change is one line.
The "it is in the config" excuse. Configuration values need names too. timeout: 30000 in a YAML file -- 30000 what? Add a comment or use a descriptive key: http_timeout_ms: 30000.
Scattering the same literal. "application/json" appears in 8 files. One of them typos it as "applicaton/json". With CONTENT_TYPE_JSON, the typo is caught at import time.
Working implementations in examples/:
examples/constants-vs-literals.md -- Multi-language examples showing magic values replaced with named constants and enums in Python, TypeScript, and GoWhen reviewing code for magic values:
_SECONDS, _MS, _BYTES)DRY rule for values is applied: more than once = must name, once but unclear = should name, once and obvious = may inlinetools
This skill should be used when the user invokes any /plan-* command from the planning-tools plugin (/plan-context, /plan-master, /plan-open-questions, /plan-verify, /plan-tick, /plan-progress, /plan-delete), asks how Claude Code's plan files work, asks where plans are stored, asks to author or audit a multi-phase master planning document, asks how to walk through a plan's Open Questions interactively, asks how to write progress entries, or mentions ~/.claude/plans/ or .claude/planning-tools.local.md. Provides the index of planning-tools commands, the master-plan workflow lifecycle, the v0.3.0+ list-shape mandate (phases and questions as headings + bulleted scope items, never tables), the v0.3.2+ plain-bullet shape (no `- [ ]` checkboxes — heading emoji is the sole tick signal), the progress-entry methodology, and the mechanics of Claude Code's plan-mode file storage.
testing
This skill should be used when the user is adjusting spacing, padding, margins, content density, section gaps, vertical rhythm, or separation between elements. Also applies when reviewing whether a design feels cramped or too sparse, choosing between borders and whitespace for separation, or defining a spacing system. Covers the 4px/8px spacing system, macro vs micro whitespace, content density spectrum, separation techniques (whitespace > background shifts > borders), and vertical rhythm.
development
This skill should be used when the user is defining brand personality in design, choosing between illustration and photography, adding motion or animation, creating visual motifs, ensuring layout variety, customizing CSS framework defaults, or calibrating the level of creative expression for a given context. Covers Lavie & Tractinsky's expressive aesthetics, the expression spectrum (restrained to bold), brand personality translation, illustration systems, photography direction, and template independence.
development
This skill should be used when the user is establishing visual importance, designing headings, creating focal points, designing CTAs or buttons, arranging label-data relationships, implementing scanning patterns (F-pattern, Z-pattern), or ensuring one dominant element per screen. Covers the three levers of hierarchy (size, weight, color), three-tier information architecture, the 'emphasize by de-emphasizing' principle, CTA design, and label-data relationships.