Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add MylesMCook/simple-code
Or install specific skill: npx add-skill https://github.com/MylesMCook/simple-code
# Description
Reduce incidental complexity in code and design. Use when writing, reviewing, refactoring, or structuring code and when deciding API shape, module boundaries, layering, naming, error handling, testing strategy, or architecture. Prefer concrete, local, reversible solutions over speculative abstraction. Do not use as sole guidance when security, compliance, formal verification, advanced type-level design, or hot-path optimization is the dominant concern.
# SKILL.md
name: simple-code
description: Reduce incidental complexity in code and design. Use when writing, reviewing, refactoring, or structuring code and when deciding API shape, module boundaries, layering, naming, error handling, testing strategy, or architecture. Prefer concrete, local, reversible solutions over speculative abstraction. Do not use as sole guidance when security, compliance, formal verification, advanced type-level design, or hot-path optimization is the dominant concern.
Simple Code
Cross-language software design guidance for code that needs to stay understandable under change. It is grounded primarily in four works: The Grug Brained Developer (Carson Gross), Worse Is Better (Richard Gabriel), A Philosophy of Software Design (John Ousterhout), and The Laws of Simplicity (John Maeda), plus a small set of explicitly named supporting heuristics in references/provenance.md.
Central claim: most maintainability problems come from incidental complexity introduced by premature abstraction, needless indirection, speculative architecture, and interfaces that leak internal problems to callers.
Scope
Use this skill for mainstream application, service, library, and tooling work when the question is mainly about structure, simplicity, maintainability, or tradeoffs.
Typical triggers:
- shaping APIs or module boundaries
- choosing between a concrete solution and a reusable abstraction
- reviewing wrappers, layers, configuration, or service splits
- planning safe refactors or incremental migrations
- deciding what to test and at what boundary
- writing code that should stay easy to understand
Do not let this skill dominate when another concern is primary: security, privacy, accessibility, compliance, formal verification, low-level performance work, memory layout, lock-free or highly concurrent algorithms, or advanced type-level design.
Defaults, Not Laws
These are strong defaults, not universal bans. Use them to remove incidental complexity. Do not apply them mechanically when the domain genuinely benefits from stronger typing, added structure, explicit protocols, or specialized architecture.
Precedence
Do not simplify away correctness, security, privacy, accessibility, data integrity, legal or regulatory requirements, or explicit user requirements. When complexity is necessary, isolate it, explain why it exists, and keep it from spreading.
When Principles Conflict
- Preserve correctness, security, data integrity, and explicit requirements first.
- For public APIs and long-lived boundaries, prefer interface simplicity.
- For first internal implementations and exploratory work, prefer implementation simplicity and reversibility.
- Keep behavior local until a stable cut point emerges; then hide complexity behind a deep interface.
Operating Procedure
When this skill is active:
- Identify the explicit requirements, constraints, dominant risks, and the concern that should lead the decision.
- Recommend the simplest design that satisfies those requirements now.
- Prefer local, concrete, reversible changes over speculative abstractions or large rewrites.
- Keep the rationale proportional. Use the philosophy to improve the answer, not to lecture.
- If complexity is required, say where it belongs and why it is necessary.
- If another discipline dominates, say so clearly and defer to it.
- Load only the reference file or files that directly deepen the current task.
Response Pattern
Favor this shape when giving advice:
- Recommendation: the simplest viable approach.
- Why: what complexity it removes or contains.
- Necessary complexity: what still must remain and why.
- Next step: the smallest reversible action that moves the work forward.
Prime Directive
When facing a design decision, ask: does this reduce incidental complexity or move it somewhere worse? If the added complexity is speculative, do not add it yet. If the added complexity is real and justified, trap it behind the simplest boundary you can.
Core Decision Rules
1. Say No First
The cheapest complexity is the complexity you never add. Default to “no” or “not yet” for new features, abstractions, parameters, configuration knobs, architectural layers, and dependencies unless the need is concrete.
When the requirement is real, build the smallest version that delivers most of the value. The long tail is where maintenance cost hides.
2. Ship the Simple Thing That Works
For internal implementations, prefer the approach with the simpler implementation when it still satisfies the real requirement. A good common-case solution now usually beats a theoretically complete one built for imagined edge cases.
Do not buy local implementation convenience by forcing every caller to absorb complexity. For public APIs and stable boundaries, simpler interfaces usually matter more than simpler internals.
3. Build Deep Modules, Not Shallow Ones
A module earns its existence when its interface is materially simpler than the implementation it hides. Small interfaces that hide meaningful work are good. Thin wrappers, pass-through layers, and tiny abstractions that expose nearly all the underlying complexity are not.
If inlining a function or removing a layer would make the system easier to understand, the boundary has not earned itself.
4. Do Not Abstract Until You Must
Write concrete code first. Let repetition reveal the real seam. A wrong abstraction is usually worse than a small amount of duplication because it hardens the wrong relationship.
Two similar call sites are usually not enough. Wait for repeated, genuinely similar pressure, then extract the smallest stable abstraction that relieves it.
5. Prefer Local and Direct Over Indirect and Distributed
Keep behavior near the place it matters. Local reasoning beats scavenger hunts across files, wrappers, and services.
Prefer:
- a self-contained function over a chain of trivial helpers
- a small local duplication over a shared utility that couples unrelated modules
- configuration close to the code it configures
- an in-process boundary over a network boundary unless a real split is justified
6. Name Things Precisely
Names are compressed design. A good name makes the code easier to read without opening the implementation. A vague name pushes explanation back onto the reader.
Break dense logic into clearly named intermediates when that improves comprehension and debugging.
if (!contact) return;
const isInactive = !contact.isActive();
const isFamilyOrFriend =
contact.inGroup(FAMILY) || contact.inGroup(FRIENDS);
if (isInactive && isFamilyOrFriend) {
sendReminder(contact);
}
If a function, type, or variable refuses to accept a precise name, treat that as a design problem.
7. Pull Complexity Downward
When complexity must exist, push it into the implementation rather than the interface. Callers should not need to learn your internals to use a module safely.
Favor sensible defaults over knobs. Favor closed sets over free-form strings when the domain is small and stable. Favor APIs that make misuse harder instead of APIs that document many footguns.
8. Write the Module Promise Before the Implementation
Before building a module, write a short note describing what it does, what it hides, and what it guarantees. If you cannot explain that clearly in a paragraph, the boundary is probably wrong or premature.
The goal is not heavy design documentation. The goal is enough clarity that the implementation has a stable target.
9. Eliminate Errors Before Handling Them
The best error handling removes entire classes of errors. Broaden input acceptance when safe. Use types, defaults, and control flow that make invalid states harder to represent.
When errors cannot be designed away, handle them at the smallest number of sensible boundaries. Avoid smearing error plumbing through many layers just because every layer can technically see it.
10. Test at Stable Boundaries
Test behavior at boundaries that should survive refactoring. Avoid tests that merely mirror internal implementation structure.
In many codebases, integration tests are the default sweet spot: high enough to verify real behavior, low enough to localize failures. Use unit, property, contract, fuzz, or end-to-end tests where they match the risk better. Mock cautiously and mostly at system boundaries.
11. Log Usefully, Optimize with Evidence
Log enough to reconstruct important decisions, failures, state transitions, and request flow. Prefer structured, correlated logs where the environment supports them. Do not log secrets, sensitive data, or high-volume branch noise.
Do not optimize on instinct. Measure first, then optimize the actual bottleneck.
Red Flags
Pause when you see these:
- a module interface as complex as the implementation it hides
- a wrapper or layer with the same abstraction as the thing underneath
- an abstraction created to unify only two call sites
- a refactor that requires many files to change before any value appears
- features or code paths being built for hypothetical future cases
- generic or type-level machinery that is harder to understand than the concrete use case
- service boundaries introduced before module boundaries are proven
- eventing or workflow machinery proposed mainly to avoid direct calls in one product flow
- tests that break mostly because names, files, or helpers moved
- difficulty giving a precise name to the thing you are building
What This Skill Does Not Mean
- It does not mean sloppy code. Simple code is precise, well named, and deliberate.
- It does not mean never abstract. It means abstract later, after the seam is real.
- It does not mean never use indirection. Use it when it hides real complexity instead of moving code around.
- It does not mean never use advanced types, concurrency, or specialized architecture. It means demand a real payoff before paying their cost.
- It does not mean ignore performance. It means optimize with measurements, not anxiety.
- It does not mean every domain can be made simple. Some complexity is inherent. Accept it, isolate it, and stop pretending it is avoidable.
- It does not replace security, database, accessibility, performance, compliance, language, framework, or domain-specific guidance. It should compose with those concerns, not overrule them.
Reference Materials
Load only what is relevant:
| Topic | Reference | When to Load |
|---|---|---|
| Source notes and provenance | references/provenance.md |
When checking where a principle comes from, how strong it is, or whether it is a primary influence versus an added heuristic |
| API and interface design | references/api-design.md |
Designing public interfaces, libraries, functions, classes, or module boundaries |
| Architecture decisions | references/architecture.md |
Choosing deployment boundaries, layering, dependencies, state, concurrency, or service and event topology |
| Refactoring and evolution | references/refactoring.md |
Refactoring existing code, shrinking systems, planning incremental migration, or touching legacy code |
# README.md
simple-code
Cross-language software design guidance for code that needs to stay
understandable under change.
simple-code helps agents reduce incidental complexity when writing,
reviewing, refactoring, or structuring code. It favors concrete, local,
reversible solutions over speculative abstraction.
Install
npx skills add https://github.com/MylesMCook/simple-code --skill simple-code
What It Covers
- API and interface design
- Module boundaries and layering
- Refactoring and incremental migration
- Testing at stable boundaries
- Error handling and naming
What It Does Not Try To Override
simple-code is a maintainability skill, not a replacement for
security, compliance, accessibility, formal verification, or specialized
performance guidance when those concerns dominate the decision.
Repository Layout
SKILL.md- the skill itselfreferences/- focused supporting guidanceevals/- prompt sets for trigger and behavior checks
License
MIT
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.