Desloppify
Use this skill for refactor passes where the main target is less code and clearer boundaries.
Primary objective:
- remove more code than you add, without regressing behavior or coverage
- make the resulting code more direct, less layered, and easier to read locally
When to Use
Trigger this skill when the user asks for:
- "desloppify"
- removing slop, boilerplate, or over-engineering
- replacing custom helpers/types/mappings with native package features
- removing TypeScript assertions and inference workarounds
Repository Overrides
Always follow repository-level agent rules (for example AGENTS.md) when they are stricter than this skill.
Value Order
During a desloppify pass, use this priority order:
- correctness
- simplicity
- deletion
- native/package-native solutions
- reuse
- type neatness
Interpretation:
- pragmatism is allowed only inside this ordering
- do not use "pragmatic" as a reason to add a new helper, facade, abstraction layer, or generic utility when simpler local code is sufficient
- if correctness requires added code at a boundary, offset it by deleting incidental complexity in the same area when practical
Modes
Use one mode per pass:
- Core mode (default)
- for most desloppify requests
- optimize for speed and high signal
- Deep mode (high-risk)
- use for auth/billing/workflows/streaming/data migrations or broad cross-layer refactors
- adds invariants, rubric, and full verification matrix
Slop Indicators
Prioritize files with:
- large modules doing multiple jobs
- custom helper stacks that wrap package-native behavior
- custom mapping layers where Zod/schema parsing would be clearer
- custom type aliases duplicating SDK/Drizzle/inference-client contracts
- repeated
as assertions, especially double-casts (as unknown as)
- duplicate or near-duplicate functions that differ only in small branches
- mirrored state (
ref/useState) that can be derived from existing source state
- conditional rendering that causes layout shift —
if (isPending) return null or if (!data) return null for UI that occupies space. Always render a skeleton/placeholder with the same dimensions instead. return null is only acceptable for truly optional UI that doesn't affect layout (e.g. a badge that may or may not appear)
- magic string identity fallbacks —
userId = x ?? y ?? "api-key" or ?? "unknown" or ?? "" where a real ID is required. If a value must exist, guard and fail; never silently degrade to a shared string that corrupts rate limits, billing, and audit trails
- permission checks missing organizationId — every
hasPermission call MUST pass organizationId explicitly. Omitting it lets Better-Auth fall back to the session's activeOrganizationId, enabling cross-org access
protectedProcedure on session-only handlers — if a handler dereferences context.user.id/.email/.name without a null check, it must use sessionProcedure (API keys have context.user = undefined)
- org-level-only API key checks —
apiKey.organizationId === resource.organizationId without scope enforcement (hasKeyScope, getAccessibleWebsiteIds, hasWebsiteScope) lets any org key access any resource in the org regardless of intended restrictions
Core Mode (Default)
Apply these non-negotiables:
- prefer native SDK/npm helpers over custom helpers
- parse untrusted boundary input with Zod (or existing boundary schema)
- derive types from source-of-truth types/schemas instead of writing parallel interfaces
- remove avoidable assertions (
as, especially as unknown as)
- merge/delete duplicate helpers and thin wrappers
- infer state instead of mirroring it
- keep one source of truth per concern
- replace, then delete obsolete code in same pass
- reduce concepts and lines, not just move code around
- keep behavior stable and tests green
- prefer inlining over extraction during a cleanup pass
- do not optimize for future reuse unless it clearly deletes more code than it adds
- default outcome should be negative LOC; if not, treat the pass as suspect and justify why
- if a refactor adds safety code, run a follow-up compression pass before calling the work done
Core execution order:
- scan hotspots (
duplicate helpers, assertions, manual mappings, mirrored state)
- identify native/schema replacement primitives
- refactor call-sites, then delete old helpers/types
- remove any new wrapper/helper that is not strictly necessary
- run required QA gates and report net reduction/results
Deep Mode (High-Risk)
Use this when touching sensitive or cross-layer behavior.
Track Selection (Required in Deep Mode)
Choose one or more tracks before editing:
- UI/state track
- API/boundary track
- Data/Drizzle track
- Cross-layer integration track
If multiple tracks apply, run in this order: API/boundary -> Data/Drizzle -> UI/state -> integration validation.
Invariant-First Refactor (Required in Deep Mode)
Before refactoring:
- list invariants that must remain true (contracts, ordering, auth, billing semantics, UX states)
- perform refactor
- prove invariants with tests or explicit checks
Quality Rubric (Required Before Done in Deep Mode)
Confirm all are satisfied:
- correctness: no behavior/regression drift beyond intended scope
- boundary safety: untrusted input validated at edges
- type integrity: no avoidable assertions or inference bypasses
- simplicity: fewer concepts/helpers/types than before
- test confidence: relevant verification layers executed
Required Patterns
Prefer Native Over Custom
- do not write custom utilities if package-native behavior already solves it
- avoid creating custom "normalizer" types when SDK/DB types can be derived directly
- prefer direct code at the call-site over a new shared helper when the logic is short and local
Prefer Zod Over Manual Mapping
- use
z.object, z.enum, preprocess, and transform for inbound payload normalization
- infer types from schemas (
z.infer) instead of parallel hand-written interfaces
- centralize shared request/response schemas where multiple call-sites parse the same shape
Drizzle Typing
- derive row types from schema:
typeof table.$inferSelect
typeof table.$inferInsert
- do not cast inserted/selected rows when derivation can model it directly
Inference Client Typing
- parse unknown JSON payloads through explicit object guards/schema helpers
- avoid structural casts of parsed responses
- keep event parsing runtime-safe and typed at the boundary
Duplicate Function Policy
- before writing a helper, search for equivalent behavior in:
- local module
- shared utils
- package SDK/native APIs
- prefer deleting both helpers and writing one direct local implementation if that is simpler than introducing another shared abstraction
- if two helpers share most logic, merge to one implementation with explicit params
- delete thin wrappers that only rename args or forward calls unchanged
- no duplicate helper/functions in the same slice unless a clear boundary requires it
- if duplication remains, document why it is intentional
Anti-Abstraction Policy
During a desloppify pass, these are presumed wrong unless clearly justified:
- new facades around test doubles or mocks
- new "shared" helpers introduced only to avoid a few repeated lines
- generic parser/normalizer utilities when a local schema or direct check is clearer
- helper extraction that makes the reader jump across files for short logic
Use this rule:
- prefer a little duplication over a new abstraction
- only introduce a helper when it removes more total code and concepts than it introduces
- if the branch becomes materially larger, do another pass focused only on deletion and inlining
Assertion Policy
- forbidden:
as unknown as, as any, broad structural casts to force compatibility
- allowed:
as const for literal narrowing
- rare escape hatches must be documented inline with a concrete reason
State Inference Policy
- derive from strongest source first:
- persisted/server/DB truth
- route/query params
- schema-validated payloads
- local transient UI state
- avoid storing derivable values (counts, flags, filters, status labels) as mutable state
- when state must exist, store minimal primitives and derive the rest
- mirrored state is only allowed with explicit justification (performance or lifecycle boundary)
Boundary Contract Policy
- parse external input once at the boundary (query/body/headers/events/webhooks)
- pass inferred typed payloads inward; do not re-parse/re-map at each layer
- keep boundary schema close to endpoint/adapter and shared only when reused
Verification Matrix (Required in Deep Mode)
Map changed files to required test layers:
- local utility/component logic -> unit
- API handlers/data access/boundary parsing -> integration
- queue/workflow/stream/state lifecycle changes -> integration + e2e
- auth/billing/permission/routing behavior -> integration + e2e
Fast Discovery Pass
Before refactoring, run quick scans:
- duplicate candidates: repeated function names or repeated logic blocks
- assertion hotspots:
as, as unknown as, broad casts
- schema drift: hand-written interfaces near existing Zod/SDK/Drizzle types
- mirrored state: watchers that only assign one variable to another
Quality Gate
After code changes (non-markdown), run:
- format/fmt
- lint
- typecheck
- unit tests
- integration tests
- e2e tests
If any layer is blocked by environment, record the exact blocker and still run all remaining layers.
Desloppify Failure Modes
Treat the pass as failing its goal if any of these are true:
- the branch adds more helpers/facades than it deletes
- net LOC increases without a narrow, concrete boundary-safety justification
- code becomes more reusable but not simpler
- local readability gets worse because logic moved into generic utilities
- the result is "cleaner architecture" but not more straightforward code
Output Checklist
Report:
- what was deleted/replaced
- where assertions were removed
- net line-count impact
- if net LOC increased, explain exactly why and what compression pass was attempted
- duplicate helpers/functions merged or removed
- state that is now inferred instead of mirrored
- mode used (
Core or Deep) and why
- invariants checked and how they were validated (required in Deep mode)
- QA results and any environment blockers
1---2name: desloppify3description: Reduce codebase slop by deleting code, flattening abstractions, and replacing custom helpers/types/assertions with native SDK/npm helpers or straightforward schemas (for example Zod). Use when asked to simplify, delete code, or "desloppify" TypeScript/Bun/Nuxt code.4---56# Desloppify78Use this skill for refactor passes where the main target is less code and clearer boundaries.910Primary objective:11- remove more code than you add, without regressing behavior or coverage12- make the resulting code more direct, less layered, and easier to read locally1314## When to Use1516Trigger this skill when the user asks for:17- "desloppify"18- removing slop, boilerplate, or over-engineering19- replacing custom helpers/types/mappings with native package features20- removing TypeScript assertions and inference workarounds2122## Repository Overrides2324Always follow repository-level agent rules (for example `AGENTS.md`) when they are stricter than this skill.2526## Value Order2728During a desloppify pass, use this priority order:291. correctness302. simplicity313. deletion324. native/package-native solutions335. reuse346. type neatness3536Interpretation:37- pragmatism is allowed only inside this ordering38- do not use "pragmatic" as a reason to add a new helper, facade, abstraction layer, or generic utility when simpler local code is sufficient39- if correctness requires added code at a boundary, offset it by deleting incidental complexity in the same area when practical4041## Modes4243Use one mode per pass:44451. Core mode (default)46- for most desloppify requests47- optimize for speed and high signal48492. Deep mode (high-risk)50- use for auth/billing/workflows/streaming/data migrations or broad cross-layer refactors51- adds invariants, rubric, and full verification matrix5253## Slop Indicators5455Prioritize files with:56- large modules doing multiple jobs57- custom helper stacks that wrap package-native behavior58- custom mapping layers where Zod/schema parsing would be clearer59- custom type aliases duplicating SDK/Drizzle/inference-client contracts60- repeated `as` assertions, especially double-casts (`as unknown as`)61- duplicate or near-duplicate functions that differ only in small branches62- mirrored state (`ref/useState`) that can be derived from existing source state63- **conditional rendering that causes layout shift** — `if (isPending) return null` or `if (!data) return null` for UI that occupies space. Always render a skeleton/placeholder with the same dimensions instead. `return null` is only acceptable for truly optional UI that doesn't affect layout (e.g. a badge that may or may not appear)64- **magic string identity fallbacks** — `userId = x ?? y ?? "api-key"` or `?? "unknown"` or `?? ""` where a real ID is required. If a value must exist, guard and fail; never silently degrade to a shared string that corrupts rate limits, billing, and audit trails65- **permission checks missing organizationId** — every `hasPermission` call MUST pass `organizationId` explicitly. Omitting it lets Better-Auth fall back to the session's `activeOrganizationId`, enabling cross-org access66- **`protectedProcedure` on session-only handlers** — if a handler dereferences `context.user.id/.email/.name` without a null check, it must use `sessionProcedure` (API keys have `context.user = undefined`)67- **org-level-only API key checks** — `apiKey.organizationId === resource.organizationId` without scope enforcement (`hasKeyScope`, `getAccessibleWebsiteIds`, `hasWebsiteScope`) lets any org key access any resource in the org regardless of intended restrictions6869## Core Mode (Default)7071Apply these non-negotiables:72- prefer native SDK/npm helpers over custom helpers73- parse untrusted boundary input with Zod (or existing boundary schema)74- derive types from source-of-truth types/schemas instead of writing parallel interfaces75- remove avoidable assertions (`as`, especially `as unknown as`)76- merge/delete duplicate helpers and thin wrappers77- infer state instead of mirroring it78- keep one source of truth per concern79- replace, then delete obsolete code in same pass80- reduce concepts and lines, not just move code around81- keep behavior stable and tests green82- prefer inlining over extraction during a cleanup pass83- do not optimize for future reuse unless it clearly deletes more code than it adds84- default outcome should be negative LOC; if not, treat the pass as suspect and justify why85- if a refactor adds safety code, run a follow-up compression pass before calling the work done8687Core execution order:881. scan hotspots (`duplicate helpers`, `assertions`, `manual mappings`, `mirrored state`)892. identify native/schema replacement primitives903. refactor call-sites, then delete old helpers/types914. remove any new wrapper/helper that is not strictly necessary925. run required QA gates and report net reduction/results9394## Deep Mode (High-Risk)9596Use this when touching sensitive or cross-layer behavior.9798### Track Selection (Required in Deep Mode)99100Choose one or more tracks before editing:1011. UI/state track1022. API/boundary track1033. Data/Drizzle track1044. Cross-layer integration track105106If multiple tracks apply, run in this order: API/boundary -> Data/Drizzle -> UI/state -> integration validation.107108### Invariant-First Refactor (Required in Deep Mode)109110Before refactoring:111- list invariants that must remain true (contracts, ordering, auth, billing semantics, UX states)112- perform refactor113- prove invariants with tests or explicit checks114115### Quality Rubric (Required Before Done in Deep Mode)116117Confirm all are satisfied:118- correctness: no behavior/regression drift beyond intended scope119- boundary safety: untrusted input validated at edges120- type integrity: no avoidable assertions or inference bypasses121- simplicity: fewer concepts/helpers/types than before122- test confidence: relevant verification layers executed123124## Required Patterns125126### Prefer Native Over Custom127128- do not write custom utilities if package-native behavior already solves it129- avoid creating custom "normalizer" types when SDK/DB types can be derived directly130- prefer direct code at the call-site over a new shared helper when the logic is short and local131132### Prefer Zod Over Manual Mapping133134- use `z.object`, `z.enum`, preprocess, and transform for inbound payload normalization135- infer types from schemas (`z.infer`) instead of parallel hand-written interfaces136- centralize shared request/response schemas where multiple call-sites parse the same shape137138### Drizzle Typing139140- derive row types from schema:141 - `typeof table.$inferSelect`142 - `typeof table.$inferInsert`143- do not cast inserted/selected rows when derivation can model it directly144145### Inference Client Typing146147- parse unknown JSON payloads through explicit object guards/schema helpers148- avoid structural casts of parsed responses149- keep event parsing runtime-safe and typed at the boundary150151### Duplicate Function Policy152153- before writing a helper, search for equivalent behavior in:154 - local module155 - shared utils156 - package SDK/native APIs157- prefer deleting both helpers and writing one direct local implementation if that is simpler than introducing another shared abstraction158- if two helpers share most logic, merge to one implementation with explicit params159- delete thin wrappers that only rename args or forward calls unchanged160- no duplicate helper/functions in the same slice unless a clear boundary requires it161- if duplication remains, document why it is intentional162163## Anti-Abstraction Policy164165During a desloppify pass, these are presumed wrong unless clearly justified:166- new facades around test doubles or mocks167- new "shared" helpers introduced only to avoid a few repeated lines168- generic parser/normalizer utilities when a local schema or direct check is clearer169- helper extraction that makes the reader jump across files for short logic170171Use this rule:172- prefer a little duplication over a new abstraction173- only introduce a helper when it removes more total code and concepts than it introduces174- if the branch becomes materially larger, do another pass focused only on deletion and inlining175176## Assertion Policy177178- forbidden: `as unknown as`, `as any`, broad structural casts to force compatibility179- allowed: `as const` for literal narrowing180- rare escape hatches must be documented inline with a concrete reason181182## State Inference Policy183184- derive from strongest source first:185 - persisted/server/DB truth186 - route/query params187 - schema-validated payloads188 - local transient UI state189- avoid storing derivable values (counts, flags, filters, status labels) as mutable state190- when state must exist, store minimal primitives and derive the rest191- mirrored state is only allowed with explicit justification (performance or lifecycle boundary)192193## Boundary Contract Policy194195- parse external input once at the boundary (query/body/headers/events/webhooks)196- pass inferred typed payloads inward; do not re-parse/re-map at each layer197- keep boundary schema close to endpoint/adapter and shared only when reused198199## Verification Matrix (Required in Deep Mode)200201Map changed files to required test layers:202- local utility/component logic -> unit203- API handlers/data access/boundary parsing -> integration204- queue/workflow/stream/state lifecycle changes -> integration + e2e205- auth/billing/permission/routing behavior -> integration + e2e206207## Fast Discovery Pass208209Before refactoring, run quick scans:210- duplicate candidates: repeated function names or repeated logic blocks211- assertion hotspots: `as`, `as unknown as`, broad casts212- schema drift: hand-written interfaces near existing Zod/SDK/Drizzle types213- mirrored state: watchers that only assign one variable to another214215## Quality Gate216217After code changes (non-markdown), run:218- format/fmt219- lint220- typecheck221- unit tests222- integration tests223- e2e tests224225If any layer is blocked by environment, record the exact blocker and still run all remaining layers.226227## Desloppify Failure Modes228229Treat the pass as failing its goal if any of these are true:230- the branch adds more helpers/facades than it deletes231- net LOC increases without a narrow, concrete boundary-safety justification232- code becomes more reusable but not simpler233- local readability gets worse because logic moved into generic utilities234- the result is "cleaner architecture" but not more straightforward code235236## Output Checklist237238Report:2391. what was deleted/replaced2402. where assertions were removed2413. net line-count impact2424. if net LOC increased, explain exactly why and what compression pass was attempted2435. duplicate helpers/functions merged or removed2446. state that is now inferred instead of mirrored2457. mode used (`Core` or `Deep`) and why2468. invariants checked and how they were validated (required in Deep mode)2479. QA results and any environment blockers