strategic-planning
Strategic Software Design Planner
Software rots because of accumulated tactical decisions — each one small and reasonable at the time, together turning a clean system into a tangle of workarounds. This skill pushes back. It teaches agents to treat design as an investment, favoring simple interfaces over simple implementations, and deep abstractions over shallow wrappers.
The ideas here come from John Ousterhout's A Philosophy of Software Design. The goal is not ceremony — it is to spend the 10–20% of planning time that prevents the next 80% of maintenance pain.
Core Principles
1. Strategic over Tactical Programming
Tactical programming is writing something that works right now. Strategic programming is writing something that is easy to change six months from now.
Every design decision is an investment. Ask: does this choice reduce future complexity, or does it just move it somewhere else? Allocate 10–20% of effort to finding the cleanest design — not because it is perfectionist, but because debt compounds faster than features.
2. Design Deep Modules
A module is deep when its interface is simple relative to the functionality it provides. A module is shallow when the interface cost approaches or exceeds the abstraction benefit.
The Unix file I/O interface (open, read, write, close) is five functions that hide tens of thousands of lines of filesystem and device complexity. That is the target. When designing a module, ask: what is the simplest interface that still lets callers ignore everything they do not need to know?
Shallow modules — thin wrappers, pass-through adapters, one-line methods with five-word names — add interface complexity without reducing cognitive load. Avoid them.
3. Information Hiding & Leakage Prevention
Every module should own its design decisions completely. If the same knowledge (a data format, a protocol detail, a configuration structure) appears in two modules, you have a leakage problem. Changes to that knowledge will require changes in multiple places.
Temporal decomposition is the most common source of leakage: structuring code around when things happen (read → parse → validate → store) rather than around what information belongs together. When you see a chain of small modules each responsible for one step, look for the information they share — that shared information should probably live in one place.
4. Somewhat General-Purpose Interfaces
The interface should be slightly more general than the current use case requires. Not wildly generic, not a framework — just enough that a second caller would not need you to change the signature.
Highly specialized behavior belongs in the application layer (above) or in a driver (below). Core modules stay generic. When you find yourself adding a parameter that only one caller will ever set, that parameter is a sign the caller should be doing more work itself.
5. Pull Complexity Downwards
When there is a choice between a complex implementation and a complex interface, choose the complex implementation. The implementation pain is paid once, by you, now. The interface pain is paid repeatedly, by every caller, forever.
Configuration parameters are a common violation: exporting a tunable that exists only because you did not want to decide a default forces every caller to understand an internal tradeoff they should never have seen. Decide the default. If it needs to be overridable, make overriding it rare and explicit.
6. Define Errors Out of Existence
Exception handling is complexity that callers cannot avoid. Design APIs so that the normal flow handles the common case cleanly, with no exception needed.
Techniques:
- Redefine semantics:
delete(file)that succeeds even when the file does not exist. The caller wanted the file gone — it is gone. - Mask at a low level: retry a transient network failure inside the module rather than surfacing it.
- Aggregate: rather than raising on the first validation error, collect all errors and return them together.
When an exception is genuinely unavoidable, keep it close to where the information exists to handle it. Do not propagate it upward until it becomes someone else's problem.
7. Design It Twice
The first design you think of is rarely the best one. It is the most obvious one, which is different.
For every major interface or module boundary, rough out two radically different approaches. They do not need to be complete — sketching the signatures and a paragraph of trade-offs is enough. Then choose, or combine the best elements. The discipline of inventing the second option forces you to understand the problem better than the first option alone allows.
8. Documentation-Driven Design
Write the interface comment before writing the implementation. If the comment requires more than three sentences to describe what the method does, the abstraction is probably wrong. A long comment is not a documentation problem — it is a design signal.
Good interface comments describe:
- What the abstraction is, not how it is implemented
- What each argument means and its valid range
- What the method guarantees after it returns (postcondition)
- What side effects the caller must know about
If you find yourself writing "this method does X except when Y, in which case it does Z, unless W is set" — stop and redesign the interface.
Planning Execution Workflow
Work through these steps before touching any implementation.
1. Decompose by information, not by execution order. List the distinct pieces of knowledge the system must manage. Each piece should map to one module. If two modules need the same information to do their job, they should probably be one module or one should own the information and expose it.
2. Design each interface twice. For each module boundary, sketch two different interface signatures. Write a one-line summary of each trade-off. Choose the deeper one.
3. Draft interface comments before any code. For each class and public method, write the comment first. If you cannot write it cleanly in three sentences, iterate on the interface — not the comment.
4. Audit for error elimination. For each exception the current design would raise, ask whether the module's semantics can be redefined to make it unnecessary. If not, decide whether to mask, aggregate, or expose — and document the choice.
5. Check layer abstraction. Each architectural layer should provide an abstraction that is meaningfully different from the layers above and below it. If a layer is mostly forwarding calls with minor changes, it is probably not carrying its weight. Either push its logic into an adjacent layer or find the hiding it should be doing.
More from vmvarela/skills
methodical-programming
Apply rigorous, mathematically-grounded program construction and verification. Derive correct programs from formal pre/post specifications using axiomatic semantics, structural induction, recursive design with bounding functions, algorithm immersion, and iterative derivation with loop invariants. Language-agnostic. Use this skill whenever the user wants to implement a function correctly, reason about loops or recursion, write formal specifications, derive tests from postconditions, prove termination, design data structures algebraically, or ensure program correctness by construction — even if they don't use terms like "formal methods" or "specification".
38pragmatic-docs
Write concise, useful project documentation (READMEs, guides, docs/) inspired by Philip Greenspun's pragmatic style. Use when creating or improving README.md, writing module docs, CONTRIBUTING.md, architecture docs, CHANGELOG, or structuring any project documentation for software projects. Use this skill whenever the user needs to write, rewrite, or review any form of project documentation — even if they just say "write a README" or "document this" without asking for a specific style.
30github-scrum
Manage software projects with Scrum on GitHub. Plan MVPs, maintain a Product Backlog as Issues, run Sprints as Milestones, and automate setup with the gh CLI. Adapted for solo developers and small teams (1-3 people). Use this skill whenever the user mentions sprints, issues, backlog, milestones, pull requests, project planning, releases, retrospectives, or any aspect of managing software work on GitHub — even if they don't explicitly mention Scrum.
27github-jira
Manage software projects with Scrum using JIRA Cloud as the backlog and sprint board, GitHub for code and releases, and the native JIRA+GitHub integration for automatic synchronization. Use this skill whenever the user mentions JIRA tickets, JIRA sprints, JIRA backlog, ticket keys (e.g. ABC-123), JQL queries, wants to create or move tickets, plan a sprint, close a sprint, sync a GitHub Release with JIRA, or needs to know how to name a branch from a ticket. Also applies when the user asks how to link GitHub PRs to JIRA tickets, how to auto-label PRs from JIRA fields, or how to manage a project that has a single JIRA project for multiple GitHub repositories.
2pragmatic-build
Guide the build phase of software development. Use when writing, testing, or refactoring code — to eliminate duplication, decouple modules, program deliberately, crash early on impossible states, drive design through tests, and refactor continuously. Based on the principles from The Pragmatic Programmer.
1high-value-testing
Guide the code review and testing phase. Use when evaluating code for testability, designing unit tests, or looking for edge cases — to ensure tests resist refactoring, provide fast feedback, and actually catch bugs rather than just padding coverage metrics.
1