agentsclimarketplace

Aposd

Skill mhenke/john-ousterhout-skills/skills/aposd

Strategic software design guidelines for AI coding agents. 15 behavioral rules + critique/audit commands based on A Philosophy of Software Design by John Ousterhout.

Install
npx -y skills add mhenke/john-ousterhout-skills --skill aposd

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Always-on design rules for AI coding agents. 15 principles from A Philosophy of Software Design — applies to every task without explicit invocation.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

17.6 KB, as published. Nobody here has run it

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/.

APOSD behavioral guidelines

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.

Scope

In scope: Design analysis, modular decomposition, interface design, naming, error-handling strategy, abstraction layering, code review with a design lens.

Deliberately out of scope:

  • Low-level debugging or performance profiling
  • Framework-specific patterns (React, Django, etc.)
  • CI/CD configuration, deployment, infrastructure
  • Security auditing — see the dedicated security skills

Boundary with sibling skills: This skill provides design principles and behavioral guidance. The sibling skills below cover formal evaluation passes:

SkillPurposeWhen to Use
aposd (this skill)Always-on design principles during codingEvery task with design decisions
aposd-critiquePrinciples-based design evaluationDeep design review, second opinion on complexity
aposd-auditSeverity-scored design auditBefore major refactoring, baseline current state

When Not to Use

This skill is designed for non-trivial design decisions. Skip it for:

  • Trivial one-line fixes (typo, variable rename, config change)
  • Mechanical refactors with no design impact (rename a method, extract a constant)
  • Exploratory code where the design is intentionally provisional

The tradeoff note at the top of Principles covers this: "For trivial tasks, use judgment."

Invocation Modes

Three invocation patterns — see references/routing.md for the full routing table and fallback behavior.

Setup

Quick start

  1. Load project context (README, CLAUDE.md, existing code)
  2. Identify task type (bug fix, feature, refactor)
  3. Scan for design red flags (shallow modules, info leakage, pass-through methods)
  4. Apply the relevant principle — the first three below cover most cases

Full setup

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.

Input / Output

This skill modifies agent behavior during any coding task:

  • Input — A coding task: writing new code, reviewing existing code, refactoring, debugging, or designing.
  • Output — Code modified by the 15 principles: deeper module boundaries extracted, pass-through layers eliminated, vague names replaced, error cases removed from caller paths. The Quick Reference table below maps each principle to its observable effect.
  • Fallback — If the task is trivial, out of scope, or doesn't match a design scenario, the skill remains silent and normal agent behavior proceeds without APOSD guidance.

Red Flags — STOP and Start Over

  • Using APOSD vocabulary to justify a tactical patch
  • Writing implementation before the interface comment
  • Exposing implementation details in the interface
  • Special-case logic embedded in a general-purpose mechanism
  • Pass-through methods or variables that add no abstraction value
  • Requiring the caller to do complex setup or handle error cases
  • Being unable to name a concept without vague or multi-word terms
  • Error-handling code that mirrors the happy path
  • Adding hooks for hypothetical future requirements
  • Decomposing work by execution order instead of abstraction boundaries
  • Making any change without leaving the surrounding module cleaner

Review discipline

When evaluating or modifying design, do not stop at the first workable answer.

  1. Identify the smallest meaningful target and the caller-facing problem.
  2. Compare at least two plausible designs or fixes before choosing one.
  3. Tie every recommendation to a concrete design principle or red flag.
  4. Prefer the change that removes future decisions from callers and leaves the module cleaner.
  5. Keep the everyday skill lightweight; use the deeper critique and audit skills when you need a formal review pass.

Principles

Tradeoff: Bias toward strategic design. For trivial tasks, use judgment.

1. Strategic Over Tactical

Working code isn't enough. Invest in design.

  • Every task gets a design investment: one improvement in the surrounding code.
  • Watch for the "tactical tornado" — churning out code quickly while accumulating debt.
  • Strategic thinking costs no extra time.
  • BEFORE implementing, state the strategic alternative: "[quick fix] vs [redesign]. Recommend strategic unless time constraints require otherwise."
  • Red flag: Using APOSD vocabulary to justify a tactical patch — language doesn't make it strategic, design investment does.

2. Design Deep Modules

Simple interface, powerful implementation.

  • Module depth = benefit / interface complexity. Maximize the ratio.
  • If the interface is as complex as the implementation, it's shallow — merge or redesign.
  • Diagnostic question: "How many use cases will this serve?" (if one, too special-purpose).

3. Information Hiding

Expose only what callers need.

  • If implementation details leak into the interface, stop and redesign.
  • If the same design decision appears in multiple modules, that's information leakage — consolidate.
  • Don't expose internal state unless callers genuinely need it.

4. Design General-Purpose Modules

Serve multiple use cases through a stable interface.

  • Design modules to be slightly more general-purpose than your current need. A general-purpose module is reused, not duplicated, and its interface stays stable across use cases.
  • If a module or method serves only one caller, it may be too special-purpose — generalize it.
  • Diagnostic questions: "In how many situations could this method be used?" (if one, too special). "Is this API easy to use for my current need?" (if not, redesign). "Can several special-purpose methods be replaced by one general-purpose method?"
  • Red flag: special-case logic embedded in a general-purpose mechanism — separate the two so the mechanism stays reusable.

5. Different Layer, Different Abstraction

Each layer must add value — pass-throughs are noise.

  • Every software layer should provide a different abstraction from the layers above and below it. If two adjacent layers have similar abstractions, one of them isn't adding value.
  • Pass-through methods (method body only delegates with the same signature) and pass-through variables (config threaded through layers that don't use it) are red flags — eliminate them.
  • Diagnostic question: "Does removing this layer change anything meaningful for the caller?" If not, delete it.

6. Pull Complexity Downward

Handle complexity in the implementation, not the interface.

  • The common case must be trivial for the caller.
  • Push inherent complexity into the module so callers don't see it.
  • Don't let the module grow into a god class.

7. Comments First

Comments are essential, not a failure. If a comment is hard to write, the design is wrong.

  • Draft the interface comment before writing the body.
  • Interface comments: what the method does for callers, not how.
  • Implementation comments: why this approach, not what the code does.
  • If a comment is hard to write or long, the design is wrong — redesign.
  • Note: Without comments, every method's contract is unspecified — readers must read the full implementation.

8. Choosing Names

Names should create an image — precise, consistent, no extra words.

  • If you can't find an intuitive name, you don't understand the concept well enough — redesign, don't rename.
  • Avoid vague names: data, info, tmp, handle, process, util, helper, manager, stuff, thing.
  • A good name is precise enough to distinguish and short enough to read. If a name needs extra words to clarify, the concept is fuzzy.
  • Consistency across the codebase matters — the same concept should always have the same name.

9. Design for Reading

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.

  • Run the obviousness check: "Would a first-time reader understand this without effort?"
  • Eliminate special cases. Every special case adds cognitive load.
  • Good naming, simple control flow, and minimal state make code obvious. If it's not obvious, redesign — don't add comments explaining it.

10. Define Errors Out of Existence

Design interfaces so common errors can't happen.

  • Can you change the interface contract to eliminate the error case?
  • If not: mask at the right level, aggregate, or crash if recovery is meaningless.
  • Red flag: error-handling that mirrors the happy path — redesign the interface.

11. Better Together or Better Apart

Merge shared concerns. Split different abstractions.

  • Merge modules that share information, simplify the interface together, or duplicate each other.
  • Split when one is general-purpose and the other special-purpose.
  • Split methods only if the child is independently understandable.
  • Red flag: conjoined methods — if you can't understand one without the other, don't split them.

12. Design It Twice

Never accept the first design for non-trivial work.

  • Create 2+ alternatives, list tradeoffs, pick the best.
  • The second design is often better.
  • For each: interface sketch, complexity tradeoffs, which symptom it addresses.

13. Design for the Future

Identify what's likely to change and encapsulate it.

  • Encapsulate volatile parts behind stable interfaces. Don't add hooks for hypothetical futures — only for changes you have reasonable evidence will occur.
  • Prefer deletion over extension. Delete what's no longer needed.
  • Over-engineering for hypothetical futures is as bad as under-engineering for real ones.

14. Increments Are Abstractions

Decompose by abstraction boundary, not by feature.

  • A new feature should be implemented as "create the abstraction layer" first, then "build the feature on top of it."
  • Don't decompose by execution order or UI surface area. "Add bold button, then italic button, then underline button" is wrong — implement the formatting interface first, then build the toolbar on top.
  • Each increment should produce a working system at a new abstraction level, not a partial feature sliced by execution order.

15. Modify Strategically

Leave every module cleaner than you found it.

  • Stay strategic even during bug fixes or maintenance.
  • Comments stay near the code they describe, not in commit messages.
  • If your change invalidates a comment, update it.
  • Higher-level comments (design rationale, module purpose) outlast line-by-line explanations.

Robustness

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:

ConditionBehaviorRecovery
Mis-triggered (task isn't a design scenario)Falls through silentlyNormal agent behavior proceeds unaffected
Principles conflict (e.g., General-Purpose vs Simplicity)Principles have tension by designUse the complexity lens: the option that better reduces cognitive load wins
Vocabulary used without design depthAgent references APOSD but interface didn't changeCheck if the interface actually changed. If not, it's tactical regardless of language
User explicitly rejects strategic approachFalls back to tactical, documents the tradeoffState the tradeoff, document it, then proceed
Target not found or emptyReport and exitNo code changes made
Target too large (>50 files)Use sub-agents to parallelize scanningReport total files scanned

Quick Reference

PrincipleRed FlagFix
Deep ModulesInterface as complex as implementationMerge or redesign
Information HidingSame decision in multiple placesConsolidate
General-Purpose ModulesServes only one callerGeneralize the interface
Different LayerPass-through method or variable chainEliminate the layer
Pull Complexity DownwardCaller does complex setupMove into module
Comments FirstHard to write the commentRedesign interface
Choosing NamesVague name like data, handleRename to create an image
Define ErrorsError-handling mirrors happy pathChange contract
Design for FutureHooks for hypothetical scenariosOnly encapsulate known volatility

Code Templates

Deep module scaffold

Hide connection lifecycle, error handling, and retry logic behind a one-line interface.

See templates/deep-module.py for a complete working implementation.

Error-eliminating decorator

Wrap a fallible API so callers never see the error case.

See templates/error-suppressor.py for a complete working implementation.

Examples

Tactical vs strategic (Rule 1 + 2)

# 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)

Error elimination (Rule 10)

# 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()

Pass-through elimination (Rule 5)

# 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)

Design It Twice (Rule 12)

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.

Common Mistakes

MistakeProblemFix
Applying all 15 rules rigidlyRules have limitsUse the complexity lens
Calling a patch "strategic"Vocabulary ≠ design investmentIf the interface didn't change, it's tactical
Skipping "design it twice"First designs are rarely bestEven 2 minutes of alternatives improves outcomes
Writing comments after codeComments become afterthoughtsDraft interface comment before implementation
"Too small to design"Every task deserves investmentEven trivial changes should leave code cleaner
"No time to design"No time to fix it later eitherTwo minutes of alternative thinking is free
"Only serves one use case"Special-purpose code proliferatesGeneralize the interface

Commands

  • aposd critique [target] — Design evaluation against principles + tactical assessment.
  • aposd audit [target] — Design audit with severity scoring and tactical tornado detection.

Troubleshooting

See references/troubleshooting.md for the full failure-mode catalog (6+ scenarios with causes and fixes).

Related

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.