Use when multiple workflows duplicate the same operational logic, when deciding what belongs in actions vs shared services, or when refactoring repeated operational blocks across domain flows. Use when adding new features that share mechanics with existing ones.
npx skills add https://github.com/michaelshimeles/skills --skill code-structure
Two-layer separation: Actions orchestrate domain rules (the "why/when"), while a service layer centralizes reusable operational mechanics (the "how").
This prevents duplicated code, inconsistent behavior, and bugs fixed in one path but not others.
Don't use when: Logic is truly domain-specific and used by only one caller.
Orchestration Layer (Actions) Service Layer (Shared Mechanics)
├── owns business rules ├── owns reusable operations
├── owns state transitions ├── owns provider/SDK interactions
├── owns auth/ownership checks ├── owns command execution details
├── owns failure classification ├── owns health checks / readiness
├── owns retries / user-facing errors └── returns structured results
└── calls service functions
Rule of thumb:
| Design Principle | Do | Don't |
|---|---|---|
| API shape | Composable capability blocks | One giant "do everything" method |
| Inputs/outputs | Explicit params, structured returns | Hidden global state, reaching into DB |
| Migration | Extract one block, replace one caller, verify, then migrate rest | Refactor everything at once |
| Domain logic | Keep auth, policy, error classification in actions | Let service mutate domain state directly |
| Extraction trigger | Logic repeated across 2+ callers | Logic used once (over-abstraction) |
Design as capability blocks, not monoliths:
// Good: composable, each caller chooses what to use
createManagedSandbox(...)
prepareRepo(...)
detectPackageManager(...)
installDependencies(...)
runBuildCommand(...)
startSandboxRuntime(...)
Each function should:
{ ready, previewUrl, proxyPort })This lets callers choose strict vs relaxed behavior per flow.
When extracting shared logic:
| Anti-Pattern | Problem |
|---|---|
| God service | One huge function hides all control flow |
| Leaky service | Service mutates database tables directly |
| Inconsistent API | Each function uses different argument styles and error semantics |
| Over-abstraction | Extracting logic used by only one caller |
// emailService.ts — shared mechanics
export async function sendWelcomeEmail(params: { to: string; name: string }) {
const html = `<h1>Welcome ${params.name}</h1>`;
await emailProvider.send(params.to, "Welcome", html);
}
// userSignup.ts — orchestration (owns WHEN to send)
if (user.marketingOptIn) {
await sendWelcomeEmail({ to: user.email, name: user.name });
}
// adminInvite.ts — orchestration (different business rule, same mechanic)
await sendWelcomeEmail({ to: invitee.email, name: invitee.name });
New feature? → Write in action first → See repeated ops? → Extract to service
→ No repetition? → Keep in action
Your architecture in one sentence: Actions orchestrate domain rules, while the service layer centralizes reusable operational mechanics with a composable, explicit-input API.
Take michaelshimeles/code-structure from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.