Dependency Discipline — no invaders board this ship
A new dependency is a passenger you feed forever: bundle size, serverless cold-starts, a
CVE surface, a migration tax, and one more thing that can break a paying client's product.
The default answer to "let's add X" is NO — because the ship almost certainly already
carries something that does it.
The rule
Before adding ANY new npm package, external SaaS, framework, datastore, or auth system:
- Prove it isn't already here (the checklist below). If it is — use it.
- If a wired-up capability is close, extend it rather than add a rival.
- If it is genuinely new and necessary, it is a CEO-gated decision — surface the
trade-off (what it buys, bundle/cold-start cost, the maintenance tax, the alternative we
already own) and wait for explicit human approval. Log the decision.
There is exactly one auth system (Supabase), one ORM (Prisma), one deploy target (Vercel +
Railway). Do not add a second of anything that already exists.
What the ship already carries (check here first)
| Need |
Already wired — use this |
| Auth / sessions |
Supabase (only auth — never NextAuth/Clerk/Auth0/custom) |
| Database / ORM |
Supabase Postgres + Prisma 7 (+ pgvector for embeddings) |
| Hosting / cron / OG / ISR |
Vercel; long-running services on Railway |
| LLMs |
Anthropic, OpenAI, OpenRouter, Google/Gemini, Perplexity (keys in Vercel env) |
| AI voice (TTS / clone) |
ElevenLabs (lib/services/ai/voice-generation.ts) |
| AI avatar video |
HeyGen (lib/marketing-agency/heygen, heygen-avatar skill) |
| Image generation (grounded) |
generateImage()/generateBatch() in lib/services/ai/image-generation.ts — the ONLY sanctioned entry point; grounded-by-default on public/reference-library/manifest.json + private Supabase bucket reference-library-private; carpet-style-v1 LoRA (trigger ccwcarpet) auto-applies for carpet-cleaning; no owned refs ⇒ BLOCKED |
| Video render / assembly |
Remotion + FFmpeg + Playwright (lib/video, video-engine); generation is grounded-by-default too via lib/services/ai/video/generation-service.ts (GroundingBlockedError on no coverage) |
| Media processing (ffmpeg) |
Railway media worker + media_* MCP tools + scripts/ frame extraction — corpus-growth path from owned videos into the reference library |
| Web scrape |
Firecrawl (@mendable/firecrawl-js) → native fetch fallback |
| Email |
Resend + SendGrid (lib/email/queue.ts) |
| Billing |
Stripe |
| Cache / queue / rate-limit |
Redis / Upstash + BullMQ (lib/rate-limit, lib/email/queue) |
| Social distribution |
9 platforms wired (lib/social) — twitter, linkedin, instagram, facebook, tiktok, youtube, pinterest, reddit, threads |
| Search / local SEO |
GSC + GBP integrations (lib/google, google-* skills) |
| Errors |
Sentry (client-side; server-side capture is intentionally off — use structured logging via serializeError) |
| State (frontend) |
React hooks + Server Components — never Redux/Zustand |
| Data fetching (client) |
SWR with credentials:'include' |
| Validation |
Zod |
| UI |
Radix + shadcn/ui + Tailwind |
Before-you-add checklist
grep -i "<capability>" package.json # already a dependency?
grep -rli "<capability>" lib app # already wired in code?
npx vercel env ls | grep -i "<provider>" # already have the keys/account?
# and scan the skill list — a specialist skill may already own this domain
If any hit → the capability exists. Use it. Stop.
Hard NO (these are settled — do not re-litigate)
- ❌ A second auth / email / voice / video / LLM-gateway provider when one is wired.
- ❌ Redux, Zustand, tRPC, GraphQL, Server Actions for mutations — absent by design; mutations go through API routes (
architecture-enforcer).
- ❌ Any non-Supabase auth (NextAuth, Clerk, Auth0, custom JWT issuance).
- ❌ A new ORM / query builder / migration tool alongside Prisma.
- ❌
pnpm/yarn — this project uses npm.
- ❌ Heavy deps that blow the 50 MB serverless function limit or the cold-start budget.
- ❌ Direct image/video provider calls (Gemini/OpenAI/Stability/fal) or stock-photo APIs — the
static guard test
tests/unit/ai/no-direct-image-apis.test.ts fails CI on them;
useReferences:false is the sole audited escape hatch (stamped UNGROUNDED). See
.claude/rules/real-images-only.md.
When new genuinely IS justified
Rare, and always: (1) the checklist confirms nothing existing fits, (2) the cost is named
(bundle, cold-start, CVE surface, maintenance), (3) the alternative-we-own is named and
rejected for a concrete reason, (4) explicit CEO approval, (5) pinned exact version,
(6) the decision logged. Then — and only then — it boards.
A comprehensive, advanced system isn't the one with the most packages. It's the one that
does the most with the fewest moving parts it already trusts.
1---2name: dependency-discipline3description: The "no invaders" gate for Synthex. The ship already carries everything it needs. NEVER add a new npm dependency, external SaaS, framework, datastore, or auth system to solve a problem an existing system already solves — adding one is a CEO-gated decision. ALWAYS prove the capability isn't already present (grep package.json, lib/, the skills list, Vercel env) before integrating anything new. Activate on ANY request to "add a package / install / npm i / integrate X / we need a tool / use library Y / add a provider / new database".4---56# Dependency Discipline — no invaders board this ship78A new dependency is a passenger you feed forever: bundle size, serverless cold-starts, a9CVE surface, a migration tax, and one more thing that can break a paying client's product.10**The default answer to "let's add X" is NO** — because the ship almost certainly already11carries something that does it.1213## The rule1415Before adding ANY new npm package, external SaaS, framework, datastore, or auth system:16171. **Prove it isn't already here** (the checklist below). If it is — use it.182. If a wired-up capability is _close_, **extend it** rather than add a rival.193. If it is genuinely new and necessary, it is a **CEO-gated decision** — surface the20 trade-off (what it buys, bundle/cold-start cost, the maintenance tax, the alternative we21 already own) and wait for explicit human approval. Log the decision.2223There is exactly one auth system (Supabase), one ORM (Prisma), one deploy target (Vercel +24Railway). Do not add a second of anything that already exists.2526## What the ship already carries (check here first)2728| Need | Already wired — use this |29| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |30| Auth / sessions | **Supabase** (only auth — never NextAuth/Clerk/Auth0/custom) |31| Database / ORM | **Supabase Postgres + Prisma 7** (+ pgvector for embeddings) |32| Hosting / cron / OG / ISR | **Vercel**; long-running services on **Railway** |33| LLMs | **Anthropic, OpenAI, OpenRouter, Google/Gemini, Perplexity** (keys in Vercel env) |34| AI voice (TTS / clone) | **ElevenLabs** (`lib/services/ai/voice-generation.ts`) |35| AI avatar video | **HeyGen** (`lib/marketing-agency/heygen`, `heygen-avatar` skill) |36| Image generation (grounded) | `generateImage()`/`generateBatch()` in `lib/services/ai/image-generation.ts` — the ONLY sanctioned entry point; grounded-by-default on `public/reference-library/manifest.json` + private Supabase bucket `reference-library-private`; carpet-style-v1 LoRA (trigger `ccwcarpet`) auto-applies for carpet-cleaning; no owned refs ⇒ BLOCKED |37| Video render / assembly | **Remotion + FFmpeg + Playwright** (`lib/video`, `video-engine`); generation is grounded-by-default too via `lib/services/ai/video/generation-service.ts` (`GroundingBlockedError` on no coverage) |38| Media processing (ffmpeg) | Railway media worker + `media_*` MCP tools + `scripts/` frame extraction — corpus-growth path from owned videos into the reference library |39| Web scrape | **Firecrawl** (`@mendable/firecrawl-js`) → native fetch fallback |40| Email | **Resend + SendGrid** (`lib/email/queue.ts`) |41| Billing | **Stripe** |42| Cache / queue / rate-limit | **Redis / Upstash** + **BullMQ** (`lib/rate-limit`, `lib/email/queue`) |43| Social distribution | **9 platforms** wired (`lib/social`) — twitter, linkedin, instagram, facebook, tiktok, youtube, pinterest, reddit, threads |44| Search / local SEO | **GSC + GBP** integrations (`lib/google`, `google-*` skills) |45| Errors | **Sentry** (client-side; server-side capture is intentionally off — use structured logging via `serializeError`) |46| State (frontend) | **React hooks + Server Components** — never Redux/Zustand |47| Data fetching (client) | **SWR** with `credentials:'include'` |48| Validation | **Zod** |49| UI | **Radix + shadcn/ui + Tailwind** |5051## Before-you-add checklist5253```bash54grep -i "<capability>" package.json # already a dependency?55grep -rli "<capability>" lib app # already wired in code?56npx vercel env ls | grep -i "<provider>" # already have the keys/account?57# and scan the skill list — a specialist skill may already own this domain58```5960If any hit → the capability exists. Use it. Stop.6162## Hard NO (these are settled — do not re-litigate)6364- ❌ A **second** auth / email / voice / video / LLM-gateway provider when one is wired.65- ❌ **Redux, Zustand, tRPC, GraphQL, Server Actions for mutations** — absent by design; mutations go through API routes (`architecture-enforcer`).66- ❌ **Any non-Supabase auth** (NextAuth, Clerk, Auth0, custom JWT issuance).67- ❌ A new ORM / query builder / migration tool alongside Prisma.68- ❌ `pnpm`/`yarn` — this project uses **npm**.69- ❌ Heavy deps that blow the 50 MB serverless function limit or the cold-start budget.70- ❌ Direct image/video provider calls (Gemini/OpenAI/Stability/fal) or stock-photo APIs — the71 static guard test `tests/unit/ai/no-direct-image-apis.test.ts` fails CI on them;72 `useReferences:false` is the sole audited escape hatch (stamped UNGROUNDED). See73 `.claude/rules/real-images-only.md`.7475## When new genuinely IS justified7677Rare, and always: (1) the checklist confirms nothing existing fits, (2) the cost is named78(bundle, cold-start, CVE surface, maintenance), (3) the alternative-we-own is named and79rejected for a concrete reason, (4) **explicit CEO approval**, (5) pinned exact version,80(6) the decision logged. Then — and only then — it boards.8182> A comprehensive, advanced system isn't the one with the most packages. It's the one that83> does the most with the fewest moving parts it already trusts.