desloppify

$npx mdskill add databuddy-analytics/Databuddy/desloppify

Delete code and replace abstractions with native SDK helpers.

  • Simplifies TypeScript/Bun/Nuxt by removing slop and boilerplate.
  • Substitutes custom helpers with Zod schemas or standard packages.
  • Prioritizes correctness before simplicity and deletion.
  • Follows repository rules when they override skill directives.
SKILL.md
.github/skills/desloppifyView on GitHub ↗
---
name: desloppify
description: 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.
---

# 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:
1. correctness
2. simplicity
3. deletion
4. native/package-native solutions
5. reuse
6. 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:

1. Core mode (default)
- for most desloppify requests
- optimize for speed and high signal

2. 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
- **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:
1. scan hotspots (`duplicate helpers`, `assertions`, `manual mappings`, `mirrored state`)
2. identify native/schema replacement primitives
3. refactor call-sites, then delete old helpers/types
4. remove any new wrapper/helper that is not strictly necessary
5. 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:
1. UI/state track
2. API/boundary track
3. Data/Drizzle track
4. 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:
1. what was deleted/replaced
2. where assertions were removed
3. net line-count impact
4. if net LOC increased, explain exactly why and what compression pass was attempted
5. duplicate helpers/functions merged or removed
6. state that is now inferred instead of mirrored
7. mode used (`Core` or `Deep`) and why
8. invariants checked and how they were validated (required in Deep mode)
9. QA results and any environment blockers
More from databuddy-analytics/Databuddy