Abstract the provider
Skill jcdavis131/cursor-agent-skills/skills/abstract-the-provider
42 agent-discipline skills for Cursor, distilled by watching an autonomous terminal coding agent (Claude Code + Fable 5). Includes the derivation method.
npx -y skills add jcdavis131/cursor-agent-skills --skill abstract-the-providerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 26 days oldThe repository was created 26 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 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
When swapping one third-party dependency for another (Shopify→Medusa, Stripe→Paddle, OpenAI→Anthropic), prefer a provider-agnostic abstraction with the new provider as the default, rather than a hard swap. Use when the user asks to replace a vendor, when choosing an open-source alternative to a paid service, or when a feature could plausibly need a second provider later.
SKILL.md
4.4 KB, as published. Nobody here has run it
Abstract The Provider
A hard swap locks you to the new vendor. An agnostic layer with a default lets you swap again without a rewrite. The default means you ship now; the abstraction means you're not stuck.
When to abstract
- The user asks to replace a vendor ("is there an open-source alternative to Shopify?").
- The feature could plausibly need a second provider later (commerce, payments, email, auth, LLM, search).
- You're choosing between a paid and an open-source option and the user prefers optionality.
- The integration is non-trivial — a hard swap would mean a future hard swap too.
Don't abstract when:
- The integration is one function call — abstraction is more code than the call.
- The provider is genuinely irreplaceable (a specific data source only one vendor offers).
- YAGNI is clear — one provider, no realistic second, and the abstraction adds complexity.
The shape
<feature>/provider.ts # the interface (methods, types)
<feature>/providers/
medusa.ts # default implementation
shopify.ts # the old one, kept or removed
<feature>/config.ts # picks the provider, default = "medusa"
Rules:
- The interface is the stable surface. Callers depend on the interface, never on a provider.
- A default is chosen, not a wiring puzzle.
COMMERCE_PROVIDER=medusa(default) — ship now, no config required. - Each provider implements the same interface. No provider-specific methods leak into callers.
- The old provider is either kept (for migration) or removed (if fully replaced) — don't leave it half-wired.
Example (commerce)
// commerce/provider.ts
export interface CommerceProvider {
listProducts(): Promise<Product[]>;
createCheckout(items: CartItem[]): Promise<CheckoutUrl>;
}
// commerce/providers/medusa.ts (default)
export const medusa: CommerceProvider = { /* ... */ };
// commerce/config.ts
const name = process.env.COMMERCE_PROVIDER ?? "medusa";
export const commerce = providers[name];
Callers import commerce from config.ts — they never import medusa directly.
Why this beats a hard swap
- Ship now, swap later. The default gets you working today; a future swap is a new provider file + a config flip, not a rewrite.
- Open-source by default, paid as escape hatch. You can default to Medusa and add Shopify later if a paid feature is needed.
- Test surface is the interface. Mock the interface in tests; don't mock each provider.
- The "swap" decision is reversible. A hard swap is a one-way door; an abstraction is a two-way door.
Anti-patterns
- Hard swap "to keep it simple". Simpler now; the next swap is a full rewrite.
- Provider methods leak into callers.
if (provider === "medusa") ...in a page — the abstraction is broken. - No default. Forces config to ship; the default is what makes it shippable today.
- Abstracting a one-call integration. An interface for
getUnixTime()is more code than the call. - Keeping the old provider half-wired. Pick: migrate-then-remove, or keep-both-supported. Don't leave it dangling.
Pair with
dependency-hygiene— the new provider's pins should be an opt-in extra if it's heavy.match-conventions— mirror the existing provider's interface shape if one already exists.validate-gate— the interface is the test surface; mock it, don't mock the provider.
Include the go-live handoff for the default provider
After the abstraction lands, the default provider still has to be stood up. Hand off the provider-specific go-live steps so the user can take it to production without re-discovering them:
"For Medusa go-live you'll need to: stand up a Medusa backend (
npx create-medusa-app@latest), create …"
The abstraction is the wiring; the go-live is the runtime. The handoff closes the loop — without it, the user has a clean interface and no running provider. This belongs in the readiness-report's "next steps" or a follow-up message, not buried in a commit message.