From pedantic-coder
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 claudepluginhub oborchers/fractional-cto --plugin pedantic-coderThis skill uses the workspace's default tool permissions.
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, e...
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
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 inline