skills/orthogonal-code/SKILL.md
Concrete, testable heuristics for writing and reviewing orthogonal (decoupled) code. Use when: (1) Writing new modules and wanting to ensure independence from day one (2) Reviewing PRs or existing code for coupling problems (3) Refactoring tightly-coupled systems toward better separation (4) Discussions mention orthogonality, coupling, decoupling, SOLID, Law of Demeter, or connascence (5) Evaluating whether a design change will increase or decrease coupling (6) Deciding how to split responsibilities between modules Complements the `system-design` skill (deep modules, information hiding, complexity). This skill focuses specifically on component independence. Draws from The Pragmatic Programmer, SOLID principles, Law of Demeter, Connascence theory, and UNIX philosophy.
npx skillsauth add silvabyte/skills orthogonal-codeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Heuristics for building and evaluating component independence. Use alongside system-design for depth/complexity concerns.
Orthogonality: Two components are orthogonal if changing one does not require changing the other.
Non-orthogonal systems exhibit a multiplier effect—a single change ripples across many modules. Orthogonal systems localize change: each module can be developed, tested, deployed, and replaced independently.
The Helicopter Test: Pick any implementation detail (a database schema column, a serialization format, an API endpoint). Change it. How many other files must also change? If the answer is more than 1–2, coupling is too high.
Relationship to system-design: That skill covers what makes a good module (deep interfaces, information hiding, complexity management). This skill covers how modules relate to each other—minimizing the connective tissue between them.
Thirteen coupling smells. For each, ask the yes/no test question. A "yes" answer indicates a coupling problem.
Does a single logical change require edits in 3+ files?
A feature or bug fix that touches many modules means those modules share knowledge they shouldn't.
// Bad: adding a new user role requires changes in
// UserModel, AuthMiddleware, RoleValidator, AdminPanel, UserSerializer, RoleSeeder
Does any expression chain 3+ accessor calls?
Long chains reveal that a module knows the internal structure of distant objects (Law of Demeter violation).
// Bad
const zip = order.getCustomer().getAddress().getZipCode();
Does a function accept a boolean that controls its behavior mode?
A boolean parameter means the function does two different things. Callers must know internal branching logic.
// Bad
function createUser(name: string, isAdmin: boolean) {
if (isAdmin) { /* entirely different path */ }
}
Does a module directly build or mutate another module's internal types?
When module A constructs or modifies types that belong to module B's internals, A breaks if B's internals change.
// Bad: OrderService directly constructs PaymentGateway's internal request type
const req = new PaymentGatewayInternalRequest({ txnId: "...", ledgerCode: 42 });
Is there mutable state that 2+ modules both read and write?
Shared mutable state is the strongest form of coupling. Changes to how one module uses the state silently break the other.
// Bad: both OrderService and InventoryService read/write the same `stock` map
const stock: Map<string, number> = new Map(); // shared between modules
If you reorder function calls, does the system silently break?
Implicit ordering dependencies mean callers must know internal sequencing.
// Bad: must call in exact order with no enforcement
initialize();
loadConfig(); // fails silently if called before initialize()
startServer(); // corrupts data if called before loadConfig()
Does a consumer use less than half the methods on an interface it depends on?
Depending on unused methods means the consumer is coupled to changes it doesn't care about (Interface Segregation violation).
// Bad: NotificationService only needs sendEmail() but depends on all of UserService
interface UserService {
getUser(): User; updateUser(): void; deleteUser(): void;
getPreferences(): Prefs; sendEmail(msg: string): void; // only method used
}
Does a module contain new ConcreteClass() for a dependency it could receive?
Hard-coding a concrete dependency prevents swapping implementations and forces tests to use the real thing.
// Bad
class OrderService {
private db = new PostgresDatabase(); // can't swap, can't test without Postgres
}
Do 3+ parameters appear together in 2+ function signatures?
Repeated parameter groups indicate a missing abstraction. All callers are coupled to the same positional knowledge.
// Bad: (street, city, zip) repeated everywhere
function validateAddress(street: string, city: string, zip: string) { }
function formatAddress(street: string, city: string, zip: string) { }
Would changing storage format require changing the public interface?
If your public API exposes storage details (column names, serialization format), every consumer is coupled to your storage layer.
// Bad: public method exposes that storage is JSON with specific keys
function getUserData(): { json_blob: string; updated_at_epoch_ms: number } { }
Are there magic strings/numbers that must match between modules with no shared type?
When two modules agree on a value by convention rather than a shared type, renaming or changing the value silently breaks the other.
// Bad: both modules use "PREMIUM" string independently
// user-service.ts
if (user.tier === "PREMIUM") { ... }
// billing-service.ts
if (tierCode === "PREMIUM") { ... }
Can you describe the module without using "and"?
If a module has multiple reasons to change, it violates Single Responsibility. "This module handles orders and sends emails and updates inventory."
Does the unit test require real infrastructure or another module's concrete implementation?
If a "unit" test needs a database, network, or another module's real code, the module under test isn't isolated.
// Bad: "unit" test requires running database
beforeAll(async () => { await database.connect(); await database.seed(); });
Thirteen orthogonality indicators. A "yes" answer confirms good decoupling.
Can you state the module's purpose in one sentence, no "and"?
A focused module has one reason to exist and one reason to change.
// Good: "UserRepository persists and retrieves user entities."
class UserRepository { /* only persistence concerns */ }
Can you swap the implementation by only changing wiring/config?
If replacing Postgres with SQLite requires editing only the DI container or config file, the abstraction boundary is clean.
// Good: swap by changing one line of wiring
container.bind(Database).to(SqliteDatabase); // was PostgresDatabase
Can you unit test with only mocks/stubs for dependencies?
If tests need nothing beyond the module itself and lightweight test doubles, the module's boundaries are well-defined.
// Good
const repo = new UserRepository(mockDatabase);
expect(repo.findById("1")).resolves.toEqual(mockUser);
Do callers send commands rather than query state and branch?
Telling objects what to do keeps decision logic inside the module that owns the data.
// Good: caller tells, doesn't ask-then-decide
order.applyDiscount(coupon); // order decides how to apply
Does every public method have at least one external consumer?
No dead public surface area. Every exposed method justifies its existence.
Given same args, same result, no side effects?
Pure functions are inherently orthogonal—they depend on nothing beyond their inputs.
// Good
function calculateTax(amount: number, rate: number): number {
return amount * rate;
}
Are all inter-module dependencies typed as interfaces/protocols/abstract types?
Depending on abstractions rather than concretions means modules can evolve independently.
// Good
class OrderService {
constructor(private readonly payments: PaymentGateway) {} // interface, not concrete
}
Can the producer operate without knowing which consumers exist?
Event emitters don't import their listeners. Adding/removing consumers requires zero changes to the producer.
// Good: OrderService emits event, doesn't know who listens
eventBus.emit("order.placed", { orderId });
Are public types defined at the boundary, not imported from internals?
Boundary types (DTOs, API contracts) should live at the module's edge, not be re-exported internals.
// Good: public type is a boundary DTO, not an internal entity
export interface OrderSummary { id: string; total: number; status: string }
Are behavioral variations driven by config, not code kept in sync?
When behavior changes come from configuration, adding variants doesn't require coordinated code changes.
// Good: new notification channel = new config entry, no code change
const channels = config.get<NotificationChannel[]>("notifications.channels");
Can you chain output into the next module without glue code?
UNIX philosophy: each module transforms data and passes it along a standard interface.
// Good
const result = await pipeline(
readOrders,
filterActive,
calculateTotals,
formatReport
)(input);
Is coupling limited to shared names (no shared algorithms/timing/meaning)?
The weakest form of connascence—modules only agree on names of interfaces and methods. See references/connascence-taxonomy.md for the full hierarchy.
Adding a new variant requires changes in exactly one place?
When the cost of adding a new case (new payment method, new notification channel) is a single file change, the design is properly open/closed.
// Good: new payment method = one new class implementing PaymentGateway
class StripePayment implements PaymentGateway { /* ... */ }
Use this protocol to assess a module, component, or PR.
List the modules/classes/files under evaluation. Define their stated purpose.
For each module, list what it imports, constructs, or calls. Draw the dependency graph (mentally or literally).
Walk through RF1–RF13. For each, answer the yes/no question. Record which flags trigger.
Walk through GF1–GF13. For each, answer the yes/no question. Record which flags are present.
Count red flags triggered and green flags present. Map to the scale:
| Rating | Red Flags | Green Flags | Meaning | |--------|-----------|-------------|---------| | Strong | 0–1 | 8+ | Well-decoupled, changes are localized | | Adequate | 2–3 | 5–7 | Some coupling, targeted refactoring advised | | Needs work | 4–6 | 3–4 | Significant coupling, plan remediation | | Tangled | 7+ | 0–2 | Major restructuring needed |
For each triggered red flag, propose a specific refactoring. Prioritize by impact: fix shared mutable state (RF5) and shotgun surgery (RF1) before cosmetic issues like data clumps (RF9).
See references/evaluation-examples.md for two fully worked examples.
When writing new code, run through this checklist before committing:
For information hiding and interface depth concerns, defer to system-design.
When reviewing code or PRs for coupling:
new ConcreteClass() for swappable dependencies?beforeAll or beforeEach is lengthy, the module may have hidden dependencies (RF13).For depth/complexity red flags (shallow modules, pass-through methods, information leakage), invoke system-design.
tools
Weekend Business Shared Drive: Browse, search, upload, and manage files. Triggers: 'weekend business drive', 'wb drive', 'upload to brand', 'shared drive', 'WB shared drive', 'list WB files', 'find in drive'
documentation
--- name: unglaze description: Rewrite glazy, eye-glazing generated content into a tight, scannable, bullet-tight engineering voice. Use when the user says "unglaze", "ungloss this", "punch this up", "tighten this", "less glazy", "eyes glaze", "make my eyes not glaze", "cut the fluff", "rewrite punchier", "make it sharper", "less corporate", "de-fluff", "less LLM-flavored". Also auto-triggers on dissatisfaction signals like "ugh too long", "tldr this", "this is boring", "too much". Applies to PR
content-media
Transcribe audio and video files using Audetic whisper service. Use when: (1) User wants to transcribe an audio or video file (2) User has a recording and needs a text transcript (3) User wants transcript analysis (gaps, speech rate, silence detection) (4) User wants to transcribe all media files in a directory Triggers: "transcribe", "transcription", "transcript", "speech to text", "audio to text", "transcribe audio", "transcribe video", "transcribe recording"
development
Testing Trophy philosophy for JS/TS. Use when: writing tests, deciding what/how to test, reviewing tests, choosing unit/integration/E2E, mocking decisions. Triggers: "write tests", "what should I test", "test this component", "review my tests", "testing strategy", "mock this", "test setup".