From aposd-skills
Always-on design rules for AI coding agents. 15 principles from A Philosophy of Software Design — applies to every task without explicit invocation.
How this skill is triggered — by the user, by Claude, or both
Slash command
/aposd-skills:aposdThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> **Content source:** `CLAUDE.md` is the single source of truth for the 15 behavioral rules — make changes to those there first, then copy here. Sections unique to this file (Setup, Quick Reference, Common Mistakes, Invocation Modes) are maintained here directly. See ADR-005 (Intentional Condensation). Examples in `references/`.
Content source:
CLAUDE.mdis the single source of truth for the 15 behavioral rules — make changes to those there first, then copy here. Sections unique to this file (Setup, Quick Reference, Common Mistakes, Invocation Modes) are maintained here directly. See ADR-005 (Intentional Condensation). Examples inreferences/.
Design-quality guardrails for every coding task. APOSD applies 15 principles from A Philosophy of Software Design (Ousterhout) — always-on behavioral guidance that pushes toward deeper modules, cleaner interfaces, and less error handling for callers. No explicit invocation needed for everyday coding.
In scope: Design analysis, modular decomposition, interface design, naming, error-handling strategy, abstraction layering, code review with a design lens.
Deliberately out of scope:
Boundary with sibling skills: This skill provides design principles and behavioral guidance. The sibling skills below cover formal evaluation passes:
| Skill | Purpose | When to Use |
|---|---|---|
| aposd (this skill) | Always-on design principles during coding | Every task with design decisions |
| aposd-critique | Principles-based design evaluation | Deep design review, second opinion on complexity |
| aposd-audit | Severity-scored design audit | Before major refactoring, baseline current state |
This skill is designed for non-trivial design decisions. Skip it for:
The tradeoff note at the top of Principles covers this: "For trivial tasks, use judgment."
Three invocation patterns — see references/routing.md for the full routing table and fallback behavior.
Before coding: load context (README, CLAUDE.md, existing code), identify task type (bug fix, feature, refactor), and scan for design red flags (shallow modules, information leakage, pass-through methods). Skipping these produces generic output.
This skill modifies agent behavior during any coding task:
When evaluating or modifying design, do not stop at the first workable answer.
Tradeoff: Bias toward strategic design. For trivial tasks, use judgment.
Working code isn't enough. Invest in design.
Simple interface, powerful implementation.
Expose only what callers need.
Serve multiple use cases through a stable interface.
Each layer must add value — pass-throughs are noise.
Handle complexity in the implementation, not the interface.
Comments are essential, not a failure. If a comment is hard to write, the design is wrong.
Names should create an image — precise, consistent, no extra words.
data, info, tmp, handle, process, util, helper, manager, stuff, thing.If someone needs to think hard to understand it, it's not obvious. Complexity is in the eye of the reader — that's your problem to fix.
Design interfaces so common errors can't happen.
Merge shared concerns. Split different abstractions.
Never accept the first design for non-trivial work.
Identify what's likely to change and encapsulate it.
Decompose by abstraction boundary, not by feature.
Leave every module cleaner than you found it.
Degradation guarantee: When the skill can't complete, it exits silently with no side effects. No partial output, no misleading results.
This skill documents every failure path explicitly:
| Condition | Behavior | Recovery |
|---|---|---|
| Mis-triggered (task isn't a design scenario) | Falls through silently | Normal agent behavior proceeds unaffected |
| Principles conflict (e.g., General-Purpose vs Simplicity) | Principles have tension by design | Use the complexity lens: the option that better reduces cognitive load wins |
| Vocabulary used without design depth | Agent references APOSD but interface didn't change | Check if the interface actually changed. If not, it's tactical regardless of language |
| User explicitly rejects strategic approach | Falls back to tactical, documents the tradeoff | State the tradeoff, document it, then proceed |
| Target not found or empty | Report and exit | No code changes made |
| Target too large (>50 files) | Use sub-agents to parallelize scanning | Report total files scanned |
| Principle | Red Flag | Fix |
|---|---|---|
| Deep Modules | Interface as complex as implementation | Merge or redesign |
| Information Hiding | Same decision in multiple places | Consolidate |
| General-Purpose Modules | Serves only one caller | Generalize the interface |
| Different Layer | Pass-through method or variable chain | Eliminate the layer |
| Pull Complexity Downward | Caller does complex setup | Move into module |
| Comments First | Hard to write the comment | Redesign interface |
| Choosing Names | Vague name like data, handle | Rename to create an image |
| Define Errors | Error-handling mirrors happy path | Change contract |
| Design for Future | Hooks for hypothetical scenarios | Only encapsulate known volatility |
Hide connection lifecycle, error handling, and retry logic behind a one-line interface.
See templates/deep-module.py for a complete working implementation.
Wrap a fallible API so callers never see the error case.
See templates/error-suppressor.py for a complete working implementation.
# Tactical: caller manages email lifecycle
notifier = EmailNotifier()
notifier.connect()
notifier.send(user.email, message)
notifier.disconnect()
# Strategic: one-line interface, complexity inside
NotificationService().send(user, message)
# Before: every caller handles None
user = db.query("SELECT * FROM users WHERE id = ?", uid).fetchone()
if user is None:
return default_user()
# After: Optional expresses "not found" as valid state
user = db.query("SELECT * FROM users WHERE id = ?", uid).first()
# Before: controller passes through to service
def update_user(request):
return UserService().update(request.user_id, request.data)
# After: controller owns its abstraction
def update_user(request):
return self.user_service.update(request.user_id, request.data)
Scenario: a service needs to authenticate users via API keys, OAuth, and SSO.
Design A — Monolithic Auth class:
class Authenticator:
# Single class handles all strategies — 3+ methods per auth type
def authenticate_api_key(self, key: str) -> User: ...
def authenticate_oauth(self, token: str) -> User: ...
def authenticate_sso(self, assertion: str) -> User: ...
# Caller must pick the right method — tight coupling to auth type
Tradeoff: Simple to start, but every new auth type adds methods to this class. Callers must know which method to call. Testing requires exercising all strategies through one class.
Design B — Strategy pattern with pluggable providers:
class AuthProvider(Protocol):
def authenticate(self, credentials: str) -> User: ...
class Authenticator:
# One method, any provider
def authenticate(self, provider: AuthProvider, credentials: str) -> User: ...
Tradeoff: More files upfront. But callers never change — they always call authenticator.authenticate(provider, creds). New auth types mean new provider classes, no changes to Authenticator. Testing each provider is isolated.
Verdict: Design B is deeper — same caller interface regardless of auth type. The implementation complexity (which provider to use, credential format) is pushed to the provider implementations, not the caller.
More examples in references/examples.md.
| Mistake | Problem | Fix |
|---|---|---|
| Applying all 15 rules rigidly | Rules have limits | Use the complexity lens |
| Calling a patch "strategic" | Vocabulary ≠ design investment | If the interface didn't change, it's tactical |
| Skipping "design it twice" | First designs are rarely best | Even 2 minutes of alternatives improves outcomes |
| Writing comments after code | Comments become afterthoughts | Draft interface comment before implementation |
| "Too small to design" | Every task deserves investment | Even trivial changes should leave code cleaner |
| "No time to design" | No time to fix it later either | Two minutes of alternative thinking is free |
| "Only serves one use case" | Special-purpose code proliferates | Generalize the interface |
aposd critique [target] — Design evaluation against principles + tactical assessment.aposd audit [target] — Design audit with severity scoring and tactical tornado detection.See references/troubleshooting.md for the full failure-mode catalog (6+ scenarios with causes and fixes).
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
npx claudepluginhub mhenke/john-ousterhout-skills --plugin aposd-skills