@shahmilsaari/memory-core 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -156,7 +156,7 @@ cd my-api
156
156
  # 2. Initialize — answers a few questions, generates all config files
157
157
  npx @shahmilsaari/memory-core init
158
158
 
159
- # 3. Load 192 predefined best-practice rules
159
+ # 3. Load 280 predefined best-practice rules
160
160
  npx @shahmilsaari/memory-core seed
161
161
 
162
162
  # 4. Install the pre-commit hook (optional but recommended)
@@ -237,7 +237,7 @@ Uses AI search — finds related rules even if you don't use the exact words.
237
237
 
238
238
  ### `seed` — Load predefined rules
239
239
 
240
- 192 best-practice rules across all supported architectures, each with a plain-English reason explaining why the rule exists.
240
+ 280 best-practice rules across all supported architectures, each with a plain-English reason explaining why the rule exists.
241
241
 
242
242
  ```bash
243
243
  npx @shahmilsaari/memory-core seed # all architectures
@@ -388,6 +388,7 @@ Pick the one that matches how your project is structured.
388
388
  | Hexagonal | You want ports and adapters for maximum testability |
389
389
  | Next.js | Next.js 13+ with server components and server actions |
390
390
  | Laravel | Laravel with service-repository pattern |
391
+ | NestJS | Modules, guards, pipes, DTOs, repository pattern |
391
392
 
392
393
  **Frontend**
393
394
  | Profile | Use when… |
package/dist/cli.js CHANGED
@@ -214,6 +214,26 @@ var seeds = [
214
214
  { type: "rule", scope: "global", architecture: "global", title: "12-factor config", content: "All environment-specific config comes from environment variables. No config files checked into git. Follow 12-factor app methodology.", tags: ["devops", "config", "12-factor"] },
215
215
  { type: "rule", scope: "global", architecture: "global", title: "Idempotent deployments", content: "Deployments and migrations must be idempotent. Running them twice must produce the same result. No manual steps required.", tags: ["devops", "deployments"] },
216
216
  { type: "rule", scope: "global", architecture: "global", title: "Queue heavy background work", content: "Offload any operation exceeding 200ms (emails, file processing, external APIs, reports) to a background job queue. Never block HTTP responses.", tags: ["performance", "queues", "reliability"] },
217
+ // ── Universal Coding Practices ────────────────────────────────────────────
218
+ { type: "rule", scope: "global", architecture: "global", title: "Filtering and pagination always on the backend", content: "All filtering, sorting, searching, and pagination is done on the server \u2014 never on the client. The API returns already-filtered, paginated results. The client never receives a full list to filter itself.", reason: "Sending all records to the client and filtering in the browser leaks data the user should not see, kills performance on large datasets, and breaks as soon as the table grows past a few hundred rows. Backend filtering is the only approach that is secure and scalable.", tags: ["api", "performance", "security", "pagination"] },
219
+ { type: "rule", scope: "global", architecture: "global", title: "Table and data-grid filtering is always server-side", content: "Data tables and grids must send filter params (column filters, search query, sort field, sort direction, page, page size) to the API as query parameters. The server applies all filters and returns only the matching rows. Never load all rows then filter in memory on the client.", reason: "A table that loads all rows to filter in the browser will work fine at 200 rows and collapse at 20,000. Server-side filtering keeps the payload small regardless of dataset size, keeps sensitive rows hidden from unauthorized users, and means the DB index does the work \u2014 not the browser CPU.", tags: ["api", "tables", "performance", "security", "pagination"] },
220
+ { type: "rule", scope: "global", architecture: "global", title: "Remove all unused imports", content: "Delete unused imports immediately. Never leave dead imports in any file. Enable linting rules (no-unused-vars, @typescript-eslint/no-unused-vars) to enforce this automatically.", reason: "Unused imports increase bundle size, slow down IDE indexing, mislead readers into thinking a dependency is actively used, and accumulate into noise that makes real imports harder to find. They are a sign of code that was partially cleaned up.", tags: ["code-quality", "cleanup"] },
221
+ { type: "rule", scope: "global", architecture: "global", title: "No console.log in production code", content: "Remove all console.log, console.debug, and console.info from production code. Use a structured logger (Winston, Pino, etc.) with log levels. Lint rules should flag any console.* call.", reason: "console.log blocks the event loop on busy servers, cannot be controlled by log level, leaks sensitive data to server logs read by ops teams, and has no structure \u2014 it is impossible to query, alert on, or correlate with other events.", tags: ["code-quality", "logging", "security"] },
222
+ { type: "rule", scope: "global", architecture: "global", title: "No commented-out code", content: "Delete code that is no longer needed. Never comment it out and leave it. Git history is the record of what existed before \u2014 it can always be recovered from there.", reason: "Commented-out code is noise that accumulates over time. Readers must decide whether it is important, temporary, or forgotten. It never gets uncommented \u2014 it just stays there forever confusing people. Delete it. Git has it.", tags: ["code-quality", "cleanup"] },
223
+ { type: "rule", scope: "global", architecture: "global", title: "Meaningful variable and function names", content: "Name variables and functions for what they represent or do, not how they are implemented. Avoid abbreviations, single-letter names (except loop counters), and vague names like data, info, manager, helper.", reason: 'Code is read far more often than it is written. A variable named "d" or "res" forces every reader to trace backward to understand its meaning. A name like "activeUserCount" is self-documenting \u2014 no comment needed, no mental overhead.', tags: ["code-quality", "naming"] },
224
+ { type: "rule", scope: "global", architecture: "global", title: "Functions do one thing", content: "Each function has a single, clear responsibility. If a function needs more than one paragraph to describe what it does, split it. Functions that take more than 3 parameters should accept an options object.", reason: "A function that does multiple things cannot be named accurately, cannot be tested in isolation, and cannot be reused for one of its sub-tasks. Single-responsibility functions compose cleanly and each can be understood, tested, and replaced independently.", tags: ["code-quality", "srp"] },
225
+ { type: "rule", scope: "global", architecture: "global", title: "Never use magic numbers or strings", content: "Replace magic values with named constants: MAX_RETRY_COUNT = 3, not just 3. Group related constants in a constants file or enum. No raw strings for status values \u2014 use enums or const maps.", reason: "A raw number like 3 or 86400 in the middle of logic is unreadable \u2014 readers must guess what it means and why it was chosen. A named constant explains the value's purpose. When the value changes, there is one place to update, not a grep across the whole codebase.", tags: ["code-quality", "constants"] },
226
+ { type: "rule", scope: "global", architecture: "global", title: "Explicit return types on all public functions", content: "Every public function and method has an explicit return type annotation. Inferred return types are acceptable only for private helpers and simple one-liners.", reason: "An explicit return type is a contract \u2014 it tells the caller what to expect and the compiler what to enforce. Without it, an accidental change to what the function returns silently breaks callers. Public APIs especially need explicit types because they cross module boundaries.", tags: ["typescript", "code-quality"] },
227
+ { type: "rule", scope: "global", architecture: "global", title: "Never use any type", content: "Never use the any type in TypeScript. Use unknown for values of truly unknown shape and narrow with type guards. Use generics for reusable functions. Configure strict: true in tsconfig.", reason: "any disables TypeScript's type checker for that value and everything derived from it \u2014 the entire benefit of TypeScript is lost. unknown forces you to prove the shape before using the value, which is exactly what type safety means.", tags: ["typescript", "code-quality"] },
228
+ { type: "rule", scope: "global", architecture: "global", title: "Handle all promise rejections", content: "Every Promise and async function must have error handling \u2014 either a try/catch or a .catch() handler. Never let a rejected promise go unhandled. Use a global unhandledRejection handler as a last resort.", reason: "An unhandled promise rejection silently swallows the error in older Node.js versions and crashes the process in newer ones. Either outcome is bad. Explicit error handling forces you to decide what to do when something fails \u2014 ignore, retry, or propagate.", tags: ["error-handling", "async", "code-quality"] },
229
+ { type: "rule", scope: "global", architecture: "global", title: "Dates always in UTC, ISO 8601 format", content: "Store all dates in UTC. Return all dates from APIs in ISO 8601 format (2024-01-15T10:30:00Z). Convert to local timezone only at the display layer on the client.", reason: "Storing local time in the database causes DST ambiguity \u2014 the same clock time can mean two different instants. UTC is unambiguous. ISO 8601 is universally parseable. Mixing formats across APIs forces every consumer to write custom date parsing.", tags: ["data", "dates", "api"] },
230
+ { type: "rule", scope: "global", architecture: "global", title: "Validate file uploads strictly", content: "Validate uploaded files by MIME type (read magic bytes, not just extension), maximum file size, and allowed file types. Store uploads outside the web root. Scan for malware in high-risk contexts.", reason: "An extension check is trivially bypassed \u2014 rename malware.exe to malware.jpg. Magic byte validation reads the actual file header. Storing uploads in the web root allows direct execution of uploaded scripts. File upload is the most common path for RCE vulnerabilities.", tags: ["security", "file-upload"] },
231
+ { type: "rule", scope: "global", architecture: "global", title: "Never expose internal IDs in public APIs", content: "Use UUIDs or slugs as public identifiers in API URLs and responses. Never expose sequential database integer IDs to clients.", reason: "Sequential IDs leak your table size (user #4 means you have ~4 users), enable enumeration attacks (fetch /users/1, /users/2 ...), and make it trivial to guess other users' resource IDs. UUIDs are opaque and non-enumerable.", tags: ["security", "api", "privacy"] },
232
+ { type: "rule", scope: "global", architecture: "global", title: "Audit log for sensitive operations", content: "Log who did what and when for every sensitive operation: login, permission change, data export, deletion, payment. Audit logs are append-only and stored separately from application logs.", reason: `Without an audit trail you cannot answer "who deleted this record?" or "when did this user's role change?". Audit logs are required for compliance (GDPR, SOC2, HIPAA) and essential for debugging security incidents after the fact.`, tags: ["security", "audit", "compliance"] },
233
+ { type: "rule", scope: "global", architecture: "global", title: "Never log sensitive data", content: "Never log passwords, tokens, API keys, credit card numbers, SSNs, or any PII. Log the user ID and action, not the sensitive value. Scrub request bodies before logging them.", reason: "Application logs are often stored in plaintext, shipped to third-party log services, and accessible to ops teams who should not see user passwords. One accidental log line can cause a credential exposure incident and a compliance violation.", tags: ["security", "logging", "privacy"] },
234
+ { type: "rule", scope: "global", architecture: "global", title: "Dependency updates on a schedule", content: "Review and update dependencies at least monthly. Use automated tools (Dependabot, Renovate) to get PRs for new versions. Never ignore security advisories.", reason: "Outdated dependencies are the most common source of vulnerabilities in production systems. Most supply-chain attacks exploit known CVEs in packages that were never updated. Automated PRs make updates low-friction \u2014 they can be reviewed and merged in minutes.", tags: ["security", "dependencies", "devops"] },
235
+ { type: "rule", scope: "global", architecture: "global", title: "Feature flags for risky changes", content: "Deploy risky changes behind a feature flag. Enable for internal users first, then a percentage rollout, then full release. Roll back by toggling the flag, not redeploying.", reason: "A traditional deployment is all-or-nothing \u2014 if it breaks, you redeploy. A feature flag separates deployment from release. A broken flag is turned off in seconds with no deployment needed. This makes releases low-risk and rollbacks instant.", tags: ["devops", "deployment", "reliability"] },
236
+ { type: "rule", scope: "global", architecture: "global", title: "API contracts documented before implementation", content: "Define the API contract (request shape, response shape, status codes, error codes) before writing implementation code. Use OpenAPI spec or a shared DTO. Consumers can mock against the contract immediately.", reason: "Discovering API shape mismatches during integration costs 10x more than discovering them before writing code. An upfront contract lets frontend and backend work in parallel against the same spec. It also forces you to think about the API from the consumer's perspective first.", tags: ["api", "documentation", "design"] },
217
237
  // ══════════════════════════════════════════════════════════════════════════
218
238
  // CLEAN ARCHITECTURE
219
239
  // ══════════════════════════════════════════════════════════════════════════
@@ -399,7 +419,108 @@ var seeds = [
399
419
  { type: "rule", scope: "global", architecture: "react-native", title: "React Navigation for routing", content: "Use React Navigation with typed route params. Define all routes in a central navigation types file. Never navigate by string without types.", reason: "Untyped route strings let you navigate to routes that don't exist or pass the wrong params \u2014 crashes discovered at runtime not compile time. Typed routes catch navigation errors during development when they are cheap to fix.", tags: ["navigation", "typescript", "react-native"] },
400
420
  { type: "rule", scope: "global", architecture: "react-native", title: "Handle all loading and error states", content: "Every screen must handle loading, error, and empty states explicitly. Never render a blank screen while data is loading.", reason: "Mobile users frequently hit slow and interrupted connections. A screen that silently shows nothing while loading, or crashes on a fetch error, feels broken. Explicit states make the app feel polished and reliable on real networks.", tags: ["ux", "error-handling", "react-native"] },
401
421
  { type: "rule", scope: "global", architecture: "react-native", title: "Zustand for global state", content: "Use Zustand for global client state. Keep stores minimal \u2014 server state stays in React Query, not Zustand.", reason: "Storing server state in Zustand duplicates it and creates a sync problem \u2014 the Zustand copy and the React Query cache diverge, and you spend time writing sync code instead of features. Keep server state in React Query only.", tags: ["state", "zustand", "react-native"] },
402
- { type: "pattern", scope: "global", architecture: "react-native", title: "Optimise FlatList rendering", content: "Always provide keyExtractor, getItemLayout (if item height is fixed), maxToRenderPerBatch, and windowSize on FlatList for smooth scrolling.", reason: "Without these props, FlatList cannot calculate item positions without measuring each one, disabling scroll-to-index and causing layout jank. These optimisations are necessary for smooth 60fps scrolling on mid-range devices.", tags: ["performance", "flatlist", "react-native"] }
422
+ { type: "pattern", scope: "global", architecture: "react-native", title: "Optimise FlatList rendering", content: "Always provide keyExtractor, getItemLayout (if item height is fixed), maxToRenderPerBatch, and windowSize on FlatList for smooth scrolling.", reason: "Without these props, FlatList cannot calculate item positions without measuring each one, disabling scroll-to-index and causing layout jank. These optimisations are necessary for smooth 60fps scrolling on mid-range devices.", tags: ["performance", "flatlist", "react-native"] },
423
+ // ══════════════════════════════════════════════════════════════════════════
424
+ // NESTJS
425
+ // ══════════════════════════════════════════════════════════════════════════
426
+ // ── Modules ──
427
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Feature modules are self-contained", content: "Each feature lives in its own module folder: module, controller, service, DTOs, entities, and repository. Nothing leaks out except what is exported from the module class.", reason: "A feature module that bleeds into other folders is a module in name only. Self-containment means you can move, extract, or delete a feature by touching one folder \u2014 no ripple effect across the codebase.", tags: ["modules", "nestjs"] },
428
+ { type: "rule", scope: "global", architecture: "nestjs", title: "No circular module dependencies", content: "Modules must not depend on each other in a cycle. If A imports B and B imports A, extract the shared logic into a SharedModule or CoreModule.", reason: "Circular module dependencies cause NestJS to fail at startup with a cryptic error. More importantly, they reveal a design problem \u2014 two modules that must know about each other are probably one module, or they share something that belongs in a third.", tags: ["modules", "architecture", "nestjs"] },
429
+ { type: "rule", scope: "global", architecture: "nestjs", title: "CoreModule for app-wide singletons", content: "Create a CoreModule (imported once in AppModule) for global singletons: database connection, config, logger, event emitter. Never import infrastructure providers in feature modules directly.", reason: "Importing infrastructure into every feature module creates hidden coupling between features and infrastructure. CoreModule is the single place that wires infrastructure \u2014 feature modules consume it through DI without knowing where it came from.", tags: ["modules", "core-module", "nestjs"] },
430
+ { type: "rule", scope: "global", architecture: "nestjs", title: "SharedModule for reusable utilities", content: "Put reusable services, helpers, and custom pipes into SharedModule and export them. Feature modules import SharedModule, not individual providers.", reason: "Providing the same utility class in multiple modules creates separate singleton instances \u2014 state is not shared and behavior is inconsistent. SharedModule ensures one instance is shared across every importer.", tags: ["modules", "shared-module", "nestjs"] },
431
+ { type: "pattern", scope: "global", architecture: "nestjs", title: "Use forRootAsync for config-dependent modules", content: "Dynamic modules (database, cache) use forRoot() for static config and forRootAsync() when config depends on other providers like ConfigService.", reason: "Passing hardcoded config to TypeOrmModule.forRoot() means different values per environment require code changes. forRootAsync() injects ConfigService \u2014 the same code runs in every environment, values come from the environment, not the source.", tags: ["modules", "config", "nestjs"] },
432
+ // ── Controllers ──
433
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Thin controllers \u2014 HTTP only", content: "Controllers parse HTTP request, call one service method, return the result. No business logic, no conditionals, no DB calls. If a controller method has more than 10 lines of logic, it is too fat.", reason: "Logic in controllers is untestable without spinning up an HTTP server and cannot be reused by CLI commands, queue consumers, or scheduled jobs. Thin controllers mean your business logic is always in services \u2014 portable and unit-testable.", tags: ["controllers", "nestjs"] },
434
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Never use @Res() in controllers", content: "Do not inject the raw Express/Fastify response object with @Res(). Return values from controller methods and let NestJS serialise them. Exception: streaming responses only.", reason: "Using @Res() bypasses NestJS interceptors, exception filters, and response serialisation. Any interceptor that transforms the response silently stops working. The entire NestJS response pipeline is designed around return values, not manual res.send().", tags: ["controllers", "interceptors", "nestjs"] },
435
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Swagger decorators on every endpoint", content: "Decorate every controller with @ApiTags and every endpoint with @ApiOperation, @ApiResponse, and @ApiBearerAuth where applicable. Swagger must always be accurate.", reason: "An inaccurate Swagger doc is worse than no doc \u2014 consumers build integrations against it and discover the mismatch in production. Decorators on the controller keep the doc co-located with the code so they update together.", tags: ["swagger", "controllers", "nestjs"] },
436
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Global API prefix and versioning", content: "Set a global API prefix (api) and version prefix (v1) in main.ts via app.setGlobalPrefix(). Do not mix versioning strategies across controllers.", reason: "Inconsistent versioning \u2014 some routes with v1, some without \u2014 forces clients to maintain a map of which endpoints are versioned. A global prefix applied in main.ts means every route is versioned uniformly with one configuration change.", tags: ["versioning", "controllers", "nestjs"] },
437
+ // ── Services & Business Logic ──
438
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Services own all business logic", content: "Every business rule, calculation, and orchestration lives in a service. Services are @Injectable() and framework-agnostic \u2014 they never import @nestjs/common HTTP decorators or Express/Fastify types.", reason: "Business logic that imports HTTP types cannot run in a CLI, queue worker, or test without an HTTP context. Framework-agnostic services can be tested with plain unit tests \u2014 no HTTP setup, no mocking of request objects.", tags: ["services", "nestjs"] },
439
+ { type: "rule", scope: "global", architecture: "nestjs", title: "One service per aggregate root", content: "Create one service per domain aggregate: UserService, OrderService, ProductService. Never create a GenericService or UtilityService that spans multiple domains.", reason: "A service that handles users AND orders AND notifications is doing three jobs. Single-responsibility services are smaller, easier to test, easier to reason about, and can be replaced without touching unrelated functionality.", tags: ["services", "srp", "nestjs"] },
440
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Services depend on repository interfaces", content: "Services import IUserRepository (interface), not UserRepository (concrete class) or TypeORM Repository<User>. Bind the interface to the implementation via a custom provider token in the module.", reason: "A service that directly imports TypeORM is untestable without a real database. Depending on an interface lets you inject a mock in tests and swap PostgreSQL for another database without touching business logic.", tags: ["services", "repository", "dependency-inversion", "nestjs"] },
441
+ // ── DTOs & Validation ──
442
+ { type: "rule", scope: "global", architecture: "nestjs", title: "class-validator on every DTO property", content: "Every DTO property has at least one class-validator decorator: @IsString(), @IsEmail(), @IsUUID(), etc. ValidationPipe is registered globally with whitelist: true and forbidNonWhitelisted: true.", reason: "whitelist: true strips undeclared properties \u2014 clients cannot sneak extra fields into your payload. forbidNonWhitelisted: true rejects requests with unknown properties rather than silently ignoring them \u2014 it makes API contracts explicit and rejects bad input at the boundary.", tags: ["validation", "dto", "nestjs"] },
443
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Separate request and response DTOs", content: "CreateUserDto and UserResponseDto are different classes. Never reuse the same DTO for input and output. Response DTOs use @Exclude() and @Expose() from class-transformer to control what fields are returned.", reason: "Input and output have different shapes \u2014 input has passwords, output never should. Reusing the same DTO means accidentally exposing a field that should be hidden. Separate DTOs make each direction explicit and safe.", tags: ["dto", "security", "nestjs"] },
444
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Never return entities directly from controllers", content: "Controller methods never return TypeORM/Prisma entities. Always map to a response DTO before returning. Use plainToInstance(ResponseDto, entity) or a dedicated mapper class.", reason: "Returning a TypeORM entity sends all columns to the client \u2014 including passwords, internal IDs, and audit fields. It also couples your API shape to your database schema: any column rename becomes a breaking API change.", tags: ["dto", "security", "entities", "nestjs"] },
445
+ { type: "pattern", scope: "global", architecture: "nestjs", title: "PartialType for update DTOs", content: "Extend CreateUserDto with PartialType(CreateUserDto) to create UpdateUserDto. All fields become optional automatically with no duplication of validators.", reason: "Duplicating DTO properties in an Update class means every change to the Create DTO must be manually mirrored in the Update DTO. PartialType inherits all properties and validators and makes them optional \u2014 the definition lives in one place.", tags: ["dto", "nestjs"] },
446
+ // ── Guards & Auth ──
447
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Guards enforce all authentication", content: "Authentication is enforced by @UseGuards(JwtAuthGuard) at the global level in main.ts. Never validate tokens inside controllers or services.", reason: "Auth logic in controllers is invisible to the NestJS guard pipeline \u2014 it bypasses @Public() decorators, cannot be globally toggled, and duplicates logic across every handler. Guards run before handlers and can be applied globally with one line.", tags: ["auth", "guards", "security", "nestjs"] },
448
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Use @Public() decorator for open endpoints", content: "Apply a global JwtAuthGuard and mark public endpoints with a custom @Public() decorator that skips the guard. Never omit the guard from individual private endpoints.", reason: "Opting out of auth per-endpoint means new endpoints are unprotected by default \u2014 a new developer adds a route and forgets the guard. @Public() opt-in means new routes are protected by default. The safe choice is the default.", tags: ["auth", "guards", "security", "nestjs"] },
449
+ { type: "rule", scope: "global", architecture: "nestjs", title: "RBAC via RolesGuard and @Roles() decorator", content: "Role-based access is enforced by a RolesGuard that reads allowed roles from a @Roles() decorator. Never check req.user.role inside controllers or services.", reason: "Role checks inside service methods are invisible to the authorization layer \u2014 they cannot be audited or changed without modifying business logic. A RolesGuard makes authorization declarative: @Roles(Role.ADMIN) is self-documenting and auditable.", tags: ["auth", "rbac", "guards", "nestjs"] },
450
+ // ── Pipes ──
451
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Global ValidationPipe in main.ts", content: "Register ValidationPipe globally in main.ts: app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true })). Never register per-controller.", reason: "A per-controller ValidationPipe is one forgotten decorator away from an unvalidated endpoint. The global pipe is the safety net \u2014 every incoming payload is validated without any per-route action. transform: true auto-converts plain objects to DTO class instances.", tags: ["validation", "pipes", "nestjs"] },
452
+ { type: "rule", scope: "global", architecture: "nestjs", title: "ParseUUIDPipe on all UUID params", content: 'Apply ParseUUIDPipe to every route parameter that expects a UUID: @Param("id", ParseUUIDPipe) id: string. Never validate UUID format manually inside the handler.', reason: "Without ParseUUIDPipe, a non-UUID string reaches the service and causes a cryptic database error. ParseUUIDPipe rejects invalid UUIDs at the HTTP boundary with a 400 \u2014 the error message is clear, the service never sees garbage input.", tags: ["pipes", "validation", "nestjs"] },
453
+ // ── Exception Filters ──
454
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Global exception filter for consistent errors", content: "Register a global HttpExceptionFilter in main.ts that catches all exceptions and formats a consistent { error: { code, message } } response. Never catch exceptions inside controllers.", reason: "try/catch in controllers produces inconsistent error shapes per endpoint and makes error handling impossible to audit. A global filter is the single place all errors flow through \u2014 one change to the error format updates every endpoint simultaneously.", tags: ["exceptions", "filters", "nestjs"] },
455
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Throw NestJS exceptions from services", content: "Services throw NotFoundException, ForbiddenException, BadRequestException, ConflictException \u2014 not raw Error objects. The global exception filter catches and formats them with the correct HTTP status automatically.", reason: "Throwing raw Error from a service forces the controller or filter to guess the status code. NestJS exceptions carry the status code with them \u2014 the filter reads it and sets the correct HTTP status. NotFoundException makes intent obvious.", tags: ["exceptions", "services", "nestjs"] },
456
+ // ── Interceptors ──
457
+ { type: "rule", scope: "global", architecture: "nestjs", title: "TransformInterceptor for response envelope", content: "Wrap all success responses in a standard envelope using a global TransformInterceptor: { data: T, timestamp, path }. Controllers return raw data \u2014 the interceptor wraps it.", reason: "Wrapping responses inside controllers duplicates the envelope code across every method. An interceptor transforms the response after the controller returns \u2014 the controller stays clean, and the envelope shape is guaranteed to be consistent with no per-method effort.", tags: ["interceptors", "response", "nestjs"] },
458
+ { type: "rule", scope: "global", architecture: "nestjs", title: "LoggingInterceptor for request tracing", content: "Use a LoggingInterceptor to log every incoming request and its response time. Include method, URL, status, and duration. Never log inside individual controllers.", reason: "Logging per-controller is duplicated and inconsistent \u2014 some requests get logged, some do not. An interceptor wraps every request automatically, gives a complete audit trail, and keeps controllers free of observability concerns.", tags: ["logging", "interceptors", "nestjs"] },
459
+ // ── Repository Pattern ──
460
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Repository interface defined in domain", content: "Define IUserRepository as an interface with methods like findById, findByEmail, save, delete. The concrete implementation (TypeOrmUserRepository) lives in the infrastructure layer and is bound via custom provider.", reason: "An interface in the domain layer defines what the service needs. The implementation is an infrastructure detail. Swapping PostgreSQL for MongoDB, or adding a cache layer, requires zero changes to the service or the interface.", tags: ["repository", "dependency-inversion", "nestjs"] },
461
+ { type: "pattern", scope: "global", architecture: "nestjs", title: "Query objects for complex queries", content: "Encapsulate complex database queries in dedicated query classes (GetActiveUsersQuery) instead of growing service methods with query-building logic. Query objects are reusable and individually testable.", reason: "A service method that builds a 10-condition query is doing two jobs: business orchestration and query construction. Query objects are single-purpose \u2014 they build one query, can be tested in isolation, and can be reused across multiple services.", tags: ["repository", "query-objects", "nestjs"] },
462
+ // ── Configuration ──
463
+ { type: "rule", scope: "global", architecture: "nestjs", title: "ConfigModule with schema validation at startup", content: "Use ConfigModule.forRoot() with a Joi or zod validation schema. The app must fail to start if required env vars are missing or have the wrong type. Never access process.env directly anywhere in the app.", reason: "An app that starts without a required env var crashes at runtime when the var is first accessed \u2014 possibly minutes after deployment. Schema validation at startup fails immediately with a clear message. The error happens before any traffic hits the app.", tags: ["config", "environment", "nestjs"] },
464
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Typed config with registerAs namespaces", content: 'Group related config using registerAs("database", () => ({ url: process.env.DATABASE_URL })). Inject with @InjectConfig("database") for typed access. Never use magic strings to access config keys.', reason: 'this.configService.get("DATABASE_URL") returns unknown \u2014 a typo in the key name returns undefined and causes a silent runtime crash. Typed namespaced configs are autocomplete-friendly and catch key name errors at compile time.', tags: ["config", "typescript", "nestjs"] },
465
+ // ── Database & Entities ──
466
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Migrations for all schema changes \u2014 never synchronize", content: "Never use synchronize: true in any environment other than a throw-away local database. All schema changes are made through migrations generated via TypeORM CLI and committed to source control.", reason: "synchronize: true drops and recreates columns to match your entity definition. On a production database this destroys data. Migrations are explicit, reversible, and reviewable \u2014 every schema change is tracked in source control alongside the code that requires it.", tags: ["database", "migrations", "nestjs"] },
467
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Entities are data containers only", content: "TypeORM entities contain columns, relations, and basic column options. No business logic, no service calls, no complex instance methods. Business rules live in services.", reason: "Business logic in entities creates hidden coupling \u2014 the entity knows about services, making it impossible to hydrate without a full DI container. Entities are plain data objects; services operate on them. This makes both individually testable.", tags: ["entities", "database", "nestjs"] },
468
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Transactions at the service layer", content: "Database transactions are managed in services using DataSource.transaction() or a UnitOfWork pattern. Never start transactions in controllers or repositories.", reason: "A transaction that starts in a controller ties the HTTP request lifecycle to the transaction lifecycle \u2014 any exception from an interceptor or filter runs outside the transaction. Service-level transactions wrap exactly the business operation that needs atomicity.", tags: ["database", "transactions", "nestjs"] },
469
+ // ── Testing ──
470
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Unit test services with mocked repositories", content: "Unit tests for services use Test.createTestingModule() with mocked repository providers. Never start a real database connection for service unit tests.", reason: "A service unit test that hits a real database is an integration test that runs slowly, requires DB setup, and fails on CI without a database. Mocked repositories run in milliseconds and test the service logic in complete isolation.", tags: ["testing", "services", "nestjs"] },
471
+ { type: "rule", scope: "global", architecture: "nestjs", title: "E2E tests with SuperTest against real app", content: "E2E tests use SuperTest against the full NestJS app bootstrapped with Test.createTestingModule(). Use a separate test database. E2E tests cover critical user flows end to end.", reason: "E2E tests catch integration bugs that unit tests miss \u2014 wiring errors, guard ordering, pipe and filter interactions. Running against the real NestJS stack means the test exercises the same code path as production.", tags: ["testing", "e2e", "nestjs"] },
472
+ { type: "rule", scope: "global", architecture: "nestjs", title: "Co-locate test files with source files", content: "Place unit test files next to the files they test: user.service.spec.ts beside user.service.ts. E2E tests live in a top-level test/ directory.", reason: "Test files in a separate folder force developers to navigate away from the implementation to find the test. Co-located tests are found instantly and are obviously related to their implementation \u2014 developers are more likely to keep them updated.", tags: ["testing", "nestjs"] },
473
+ // ── Custom Decorators ──
474
+ { type: "pattern", scope: "global", architecture: "nestjs", title: "@CurrentUser() decorator for authenticated user", content: "Extract the authenticated user from the request with a custom @CurrentUser() decorator instead of @Req() req.user inside handlers.", reason: "@Req() exposes the entire request object to the controller \u2014 far more access than it needs. A @CurrentUser() decorator returns exactly the typed user object, making the controller's dependency on auth explicit and the method signature self-documenting.", tags: ["decorators", "auth", "nestjs"] },
475
+ { type: "pattern", scope: "global", architecture: "nestjs", title: "Custom decorators for repeated param extraction", content: "If you access the same request property in multiple handlers (tenant ID, correlation ID, device ID), extract it into a custom decorator. Never duplicate request parsing across handlers.", reason: 'Repeating @Headers("x-tenant-id") and parsing it in every handler that needs it is fragile \u2014 a header name change requires updating every handler. A custom decorator centralises the extraction and is changed in one place.', tags: ["decorators", "nestjs"] },
476
+ // ── Async & Events ──
477
+ { type: "rule", scope: "global", architecture: "nestjs", title: "EventEmitter for in-process side effects", content: "Use @nestjs/event-emitter to decouple side effects (send email, invalidate cache, log analytics) from the main business operation. Emit events from services, handle them in dedicated listener classes.", reason: "Calling EmailService directly from UserService creates a direct dependency \u2014 if the email service throws, the user creation fails. An event emitter decouples them: the primary operation succeeds regardless of what happens in listeners, and listeners can be added without touching the service.", tags: ["events", "async", "nestjs"] },
478
+ { type: "rule", scope: "global", architecture: "nestjs", title: "BullMQ for heavy async operations", content: "Offload time-consuming tasks (image processing, bulk emails, report generation) to a BullMQ queue. Never run operations that take more than 200ms synchronously inside an HTTP handler.", reason: "A handler that takes 5 seconds blocks the event loop and times out under load. Queues move the work off the HTTP thread \u2014 the handler returns immediately with a job ID, the queue worker processes it in the background, and the app stays responsive.", tags: ["queues", "async", "performance", "nestjs"] },
479
+ // ══════════════════════════════════════════════════════════════════════════
480
+ // SVELTE 5 + SVELTEKIT — frontend framework
481
+ // ══════════════════════════════════════════════════════════════════════════
482
+ // ── Runes & Reactivity ───────────────────────────────────────────────────
483
+ { type: "rule", scope: "global", architecture: "svelte", title: "Use $state only for reactive variables", content: "Use $state only for variables that need to be reactive. Plain let or const for everything else.", reason: "Every $state variable carries reactivity overhead. Marking non-reactive data as $state adds memory cost and obscures which values actually drive UI updates \u2014 making the component harder to reason about.", tags: ["svelte", "runes", "reactivity"] },
484
+ { type: "rule", scope: "global", architecture: "svelte", title: "Prefer $derived over $effect for computed values", content: "Use $derived (or $derived.by() for multi-statement logic) for all computed values. Never derive values inside $effect.", reason: "$derived is memoized and side-effect free \u2014 it recalculates only when its dependencies change and never triggers extra renders. $effect runs after DOM updates and computing values inside it creates subtle timing bugs and circular loops.", tags: ["svelte", "runes", "reactivity"] },
485
+ { type: "rule", scope: "global", architecture: "svelte", title: "Never update $state inside $effect", content: "Never update $state inside a $effect callback. Derive the value with $derived instead, or restructure the logic so no effect is needed.", reason: "Updating state inside an effect creates a reactivity loop: state changes \u2192 effect runs \u2192 state changes again. Svelte will warn about this and the component will re-render unpredictably. $derived eliminates the loop entirely.", tags: ["svelte", "runes", "reactivity"] },
486
+ { type: "rule", scope: "global", architecture: "svelte", title: "Treat $effect as an escape hatch", content: "Use $effect only for interacting with external systems (DOM APIs, third-party libraries, WebSockets). Never use it as the default way to react to state changes.", reason: "Overusing $effect leads to components that are hard to trace \u2014 the execution flow jumps from renders to effects unpredictably. Most logic belongs in $derived, event handlers, or templates.", tags: ["svelte", "runes", "reactivity"] },
487
+ { type: "rule", scope: "global", architecture: "svelte", title: "Use $props for all component props", content: "Declare component props with $props() in Svelte 5. Never use the legacy export let syntax in runes mode.", reason: "$props is the Svelte 5 standard \u2014 it integrates with the runes reactivity system, works correctly with TypeScript inference, and is the only prop syntax that will receive future improvements.", tags: ["svelte", "runes", "props"] },
488
+ { type: "rule", scope: "global", architecture: "svelte", title: "Mark two-way bindable props with $bindable", content: "Mark props that parents can bind to with $bindable(). All other props are read-only by convention \u2014 never mutate a non-bindable prop from the child.", reason: "Mutating a non-bindable prop in the child is undefined behaviour in Svelte 5 \u2014 Svelte warns and the parent state is not updated. $bindable() makes the two-way contract explicit and self-documenting.", tags: ["svelte", "runes", "props", "binding"] },
489
+ // ── State Management ─────────────────────────────────────────────────────
490
+ { type: "rule", scope: "global", architecture: "svelte", title: "Use runes for 95% of state management", content: "Use $state and $derived for all local and shared state in new Svelte 5 code. Stores are legacy \u2014 use them only when integrating with third-party libraries that require the store contract.", reason: "Runes use signals, work identically in .svelte and .ts files, and have no subscription/unsubscription boilerplate. Mixing runes and stores creates two mental models \u2014 prefer one system throughout.", tags: ["svelte", "state", "runes"] },
491
+ { type: "rule", scope: "global", architecture: "svelte", title: "Use context API for scoped layout state", content: "Use setContext/getContext for state shared across a component subtree (auth, theme, user preferences). Export typed getter functions from a context module for type safety.", reason: "Context avoids prop drilling for state that belongs to a whole page or layout section. Typed getter functions catch missing-context bugs at compile time instead of throwing at runtime.", tags: ["svelte", "context", "state"] },
492
+ { type: "pattern", scope: "global", architecture: "svelte", title: "Extract shared state into .svelte.ts modules", content: "Put reusable state logic (counters, toggles, form state) in .svelte.ts files using $state and $derived. Import and instantiate them in components.", reason: "Runes work identically in .svelte.ts as in components \u2014 this lets you extract and test state logic independently from the component template, and reuse it across multiple components without duplicating code.", tags: ["svelte", "state", "reusability"] },
493
+ // ── SvelteKit Load Functions ─────────────────────────────────────────────
494
+ { type: "rule", scope: "global", architecture: "svelte", title: "Use +page.server.ts for private data fetching", content: "Fetch data that requires secrets, database access, or server-only logic in +page.server.ts load functions. Use +page.ts only for public API calls that can run on both client and server.", reason: "+page.server.ts code never ships to the browser \u2014 secrets and DB credentials stay server-side. +page.ts is sent to the client as a JS bundle, leaking any secrets embedded in it.", tags: ["sveltekit", "load", "server", "security"] },
495
+ { type: "rule", scope: "global", architecture: "svelte", title: "Fetch shared data in +layout.server.ts", content: "Fetch data needed by every page in a route group (auth session, user profile, navigation) in +layout.server.ts. Never duplicate layout-level data fetching in individual page loaders.", reason: "Layout loaders run once per navigation and apply to all child routes. Duplicating this fetch in every page loader causes redundant network calls on every navigation and diverging data if not kept in sync.", tags: ["sveltekit", "load", "layout"] },
496
+ { type: "pattern", scope: "global", architecture: "svelte", title: "Stream slow data by returning promises from load", content: "Return slow data as unresolved promises from load functions rather than awaiting them. SvelteKit streams the fast data immediately and resolves slow promises in the background.", reason: "Awaiting all data before rendering makes the user wait for the slowest query. Streaming sends the page shell immediately \u2014 the user sees content within milliseconds and slow data fills in progressively, dramatically improving perceived performance.", tags: ["sveltekit", "load", "streaming", "performance"] },
497
+ // ── Form Actions ─────────────────────────────────────────────────────────
498
+ { type: "rule", scope: "global", architecture: "svelte", title: "Use use:enhance on all forms", content: "Always add use:enhance to <form> elements in SvelteKit. This enables JS-enhanced submission while keeping full-page-load as the fallback when JS is unavailable.", reason: "Without use:enhance every form submission causes a full page reload \u2014 poor UX in a SPA. use:enhance adds progressive enhancement: JS users get instant feedback, JS-disabled users still get working forms.", tags: ["sveltekit", "forms", "progressive-enhancement"] },
499
+ { type: "rule", scope: "global", architecture: "svelte", title: "Return typed errors from form actions", content: "Return field-keyed error objects from form actions using the fail() helper. Access them via form.errors in the template to display inline validation messages.", reason: "Without structured errors, you cannot show inline field errors \u2014 you can only display a generic failure message at the top of the form. Keyed errors let you show exactly which field failed and why, next to the field itself.", tags: ["sveltekit", "forms", "validation"] },
500
+ // ── Component Structure ───────────────────────────────────────────────────
501
+ { type: "rule", scope: "global", architecture: "svelte", title: "Order: script \u2192 markup \u2192 style", content: "Order every Svelte component as: <script> block, then markup/template, then <style> block. Never mix the ordering across a codebase.", reason: "Consistent ordering means every developer knows exactly where to look \u2014 props and state are always at the top, styles at the bottom. Prettier enforces this convention automatically.", tags: ["svelte", "structure", "conventions"] },
502
+ { type: "rule", scope: "global", architecture: "svelte", title: 'Use <script lang="ts"> always', content: 'Always use <script lang="ts"> in Svelte components. Enable strict TypeScript checking via svelte-check in CI.', reason: "JavaScript components have no type safety \u2014 prop shapes, event payloads, and store values are unchecked. TypeScript catches refactoring breaks, missing required props, and API shape mismatches before they reach production.", tags: ["svelte", "typescript", "quality"] },
503
+ { type: "rule", scope: "global", architecture: "svelte", title: "Keep components single-responsibility", content: "Each Svelte component does one thing. Split components when they exceed ~150 lines or contain unrelated concerns.", reason: "Large multi-concern components accumulate state entanglement \u2014 changing one feature risks breaking another. Small focused components are independently testable, reusable, and replaceable.", tags: ["svelte", "structure", "maintainability"] },
504
+ // ── Snippets & Composition ────────────────────────────────────────────────
505
+ { type: "rule", scope: "global", architecture: "svelte", title: "Prefer snippets over slots for composition", content: "Use Svelte 5 snippets ({#snippet} / {@render}) for component composition. Avoid the legacy <slot> API in new components.", reason: "Snippets are more powerful than slots \u2014 they support parameters, work inside loops, can be passed as props, and enable recursive patterns. Slots are a legacy API and will receive no new features.", tags: ["svelte", "snippets", "composition"] },
506
+ { type: "rule", scope: "global", architecture: "svelte", title: "Provide fallbacks for optional snippets", content: "Always check if an optional snippet was passed before rendering it: {#if children}{@render children()}{:else}<DefaultContent />{/if}.", reason: "Calling @render on an undefined snippet throws a runtime error. An explicit fallback makes the component usable without every optional snippet filled in \u2014 components are more robust and easier to adopt incrementally.", tags: ["svelte", "snippets", "composition"] },
507
+ // ── Performance ───────────────────────────────────────────────────────────
508
+ { type: "rule", scope: "global", architecture: "svelte", title: "Always key {#each} blocks", content: "Always provide a unique key expression in {#each items as item (item.id)} blocks. Never use array index as a key for lists that can be reordered or filtered.", reason: "Without keys, Svelte reuses DOM nodes by position \u2014 reordering or removing an item updates every node after it. With keys, Svelte moves existing DOM nodes, skipping unneeded updates. Index keys break this entirely when items are removed or reordered.", tags: ["svelte", "performance", "lists"] },
509
+ { type: "rule", scope: "global", architecture: "svelte", title: "Lazy load below-the-fold components", content: "Use dynamic import() for heavy components that appear only on interaction or below the fold: const HeavyChart = await import('./HeavyChart.svelte').", reason: "Every component in a static import is included in the initial JS bundle, increasing Time to Interactive for all users even if they never see the heavy component. Dynamic imports code-split automatically in Vite.", tags: ["svelte", "performance", "code-splitting"] },
510
+ { type: "rule", scope: "global", architecture: "svelte", title: "Never use querySelector inside components", content: "Never use document.querySelector or document.querySelectorAll inside Svelte components. Use bind:this to get element references, or Svelte actions for DOM manipulation.", reason: "querySelector queries the entire document \u2014 it bypasses Svelte's component boundary, can match elements from other components, and breaks when components are server-rendered (no document on the server).", tags: ["svelte", "dom", "performance"] },
511
+ // ── Error Handling ────────────────────────────────────────────────────────
512
+ { type: "rule", scope: "global", architecture: "svelte", title: "Custom +error.svelte for every route group", content: "Create a +error.svelte page for each route group that needs a custom error UI. Use page.status and page.error.message to display contextual messages.", reason: "Without a custom error page, SvelteKit shows a bare default error UI with no branding or recovery path. Custom error pages guide users toward recovery actions (go home, retry, contact support) and match the app's design.", tags: ["sveltekit", "errors", "ux"] },
513
+ { type: "rule", scope: "global", architecture: "svelte", title: "Use handleError hook for unexpected errors", content: "Implement the handleError hook in hooks.server.ts to sanitize error messages and send unexpected errors to a monitoring service (Sentry, Datadog). Never expose raw error details to the client.", reason: "Unhandled errors can leak stack traces, file paths, and internal logic to the browser. The handleError hook intercepts these before they reach the client \u2014 you control what the user sees and send the full error to your monitoring system.", tags: ["sveltekit", "errors", "security", "monitoring"] },
514
+ // ── Accessibility ─────────────────────────────────────────────────────────
515
+ { type: "rule", scope: "global", architecture: "svelte", title: "Never use positive tabindex values", content: 'Never use tabindex values greater than 0. Use tabindex="0" to add an element to focus order, tabindex="-1" to remove it. Let the browser manage natural tab order.', reason: "Positive tabindex values create a separate, confusing tab order that overrides the natural document flow. Keyboard users Tab through elements in a logical, surprising-to-no-one order \u2014 positive tabindex breaks that guarantee.", tags: ["svelte", "accessibility", "a11y"] },
516
+ { type: "rule", scope: "global", architecture: "svelte", title: "Use ARIA live regions for dynamic updates", content: 'Wrap dynamically updated content (notifications, status messages, search results count) in aria-live="polite" or aria-live="assertive" regions so screen readers announce the change.', reason: "Screen readers only read content the user focuses on or that's in a live region. Dynamic DOM changes \u2014 even if clearly visible \u2014 are invisible to screen reader users without a live region announcement.", tags: ["svelte", "accessibility", "a11y"] },
517
+ // ── Testing ───────────────────────────────────────────────────────────────
518
+ { type: "rule", scope: "global", architecture: "svelte", title: "Extract logic into .ts files for unit testing", content: "Extract business logic, transformations, and validation from components into plain .ts functions. Test those functions directly with Vitest \u2014 components should be tested for behaviour, not internal state.", reason: "Testing pure functions is orders of magnitude simpler than mounting a component. Logic extracted to .ts is framework-agnostic, instantly testable, and reusable. Components that contain logic tightly couple test setup to UI implementation details.", tags: ["svelte", "testing", "vitest"] },
519
+ { type: "rule", scope: "global", architecture: "svelte", title: "Use @testing-library/svelte for component tests", content: "Test Svelte components with @testing-library/svelte and Vitest. Query by role, label, and text \u2014 never by class or internal implementation details.", reason: "Testing Library queries mirror how real users interact with the UI \u2014 by what they see and can click, not by CSS class names. This makes tests resilient to markup refactors and actually verifies user-facing behaviour.", tags: ["svelte", "testing", "testing-library"] },
520
+ { type: "rule", scope: "global", architecture: "svelte", title: "Run svelte-check in CI", content: "Run npx svelte-check --tsconfig ./tsconfig.json in your CI pipeline to catch type errors across all .svelte files before merging.", reason: "The VS Code extension only checks open files. svelte-check validates the entire project \u2014 catching type errors in components you haven't recently opened. This is the only way to guarantee the full project is type-safe on every merge.", tags: ["svelte", "typescript", "ci"] },
521
+ // ── Anti-Patterns ─────────────────────────────────────────────────────────
522
+ { type: "rule", scope: "global", architecture: "svelte", title: "Do not return stores from load functions", content: "Never return writable stores from SvelteKit load functions. Return plain data and let components initialize their own reactive state from it.", reason: "Stores returned from load functions cannot be migrated to runes \u2014 they create a pattern incompatible with Svelte 5's reactivity model. Writing to shared state from load is also unsafe in SSR where multiple requests share the module scope.", tags: ["svelte", "sveltekit", "anti-pattern"] },
523
+ { type: "rule", scope: "global", architecture: "svelte", title: "Avoid options API style \u2014 runes only", content: "Do not use the Svelte 4 options-style patterns (export let, $: reactive statements, $store subscriptions) in new Svelte 5 components. Use runes throughout.", reason: "Mixing the two reactivity systems in the same codebase creates two mental models, confuses new developers, and makes future migrations harder. Svelte 5 runes supersede every Svelte 4 pattern.", tags: ["svelte", "runes", "anti-pattern"] }
403
524
  ];
404
525
 
405
526
  // src/config.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shahmilsaari/memory-core",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Universal AI memory core — generate AI context files from architecture profiles with RAG support",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,43 @@
1
+ name: nestjs
2
+ displayName: NestJS
3
+ layer: backend
4
+ description: NestJS with feature modules, DI, guards, pipes, and repository pattern
5
+
6
+ rules:
7
+ - Feature modules are self-contained — each has its own module, controller, service, DTOs, and entities
8
+ - Controllers are thin — validate HTTP input, delegate to service, return response. Zero business logic.
9
+ - Services own all business logic — never import controllers or HTTP-specific classes
10
+ - Use class-validator and class-transformer with ValidationPipe for all incoming DTOs
11
+ - Repository pattern — services depend on repository interfaces, never directly on TypeORM/Prisma
12
+ - Guards handle all authentication and authorization — never check tokens inside controllers or services
13
+ - Use exception filters for all error handling — never return raw errors or catch exceptions in controllers
14
+ - Interceptors handle cross-cutting concerns: logging, response transformation, caching
15
+ - ConfigModule with Joi/zod validation for all environment variables at startup
16
+ - Each module uses forwardRef() only as a last resort — restructure to remove circular dependencies
17
+ - Global pipes, guards, and filters are registered in main.ts — never duplicated per-controller
18
+ - Use custom decorators to extract common parameter logic from controllers
19
+
20
+ folders:
21
+ - src/modules/{feature}/
22
+ - src/modules/{feature}/{feature}.module.ts
23
+ - src/modules/{feature}/{feature}.controller.ts
24
+ - src/modules/{feature}/{feature}.service.ts
25
+ - src/modules/{feature}/dto/
26
+ - src/modules/{feature}/entities/
27
+ - src/modules/{feature}/repositories/
28
+ - src/common/guards/
29
+ - src/common/filters/
30
+ - src/common/interceptors/
31
+ - src/common/pipes/
32
+ - src/common/decorators/
33
+ - src/config/
34
+
35
+ avoid:
36
+ - Business logic in controllers
37
+ - Direct TypeORM/Prisma calls in services (use repositories)
38
+ - Circular module dependencies
39
+ - Hardcoded configuration values
40
+ - Using @Res() or @Next() in controllers — breaks interceptors and exception filters
41
+ - Global state outside the DI container
42
+ - Catching exceptions inside controllers instead of using filters
43
+ - Fat modules that own too many features
@@ -1,29 +1,57 @@
1
1
  name: svelte
2
2
  displayName: Svelte / SvelteKit
3
3
  layer: frontend
4
- description: Svelte 5 with runes, stores, and SvelteKit file-based routing
4
+ description: Svelte 5 with runes reactivity and SvelteKit routing, load functions, and form actions
5
5
 
6
6
  rules:
7
- - Use Svelte 5 runes ($state, $derived, $effect) for reactivity avoid legacy reactive declarations
8
- - Use $props() rune for component props with TypeScript types
9
- - Use Svelte stores (writable, readable, derived) for shared state across components
10
- - Keep components small and single-purposeextract logic into .ts utility files
11
- - Use SvelteKit load functions for data fetching — never fetch in onMount for SSR routes
12
- - Use form actions for mutationsavoid manual fetch() for form submissions
13
- - Co-locate component styles with scoped <style> blocksno global CSS in components
14
- - Use $effect sparingly prefer derived state over side effects
7
+ - Use $state only for variables that are reactiveplain let/const for everything else
8
+ - Use $derived (or $derived.by()) for all computed values never compute inside $effect
9
+ - Never update $state inside a $effect callback restructure with $derived instead
10
+ - Treat $effect as an escape hatch for external system interop only not as a general reaction pattern
11
+ - Use $props() for all component props — never use the legacy export let in runes mode
12
+ - Mark two-way bindable props with $bindable() all others are read-only by convention
13
+ - Use runes for local and shared statestores only for third-party library interop
14
+ - Use setContext/getContext with typed getter functions for layout-level shared state
15
+ - Extract reusable state logic into .svelte.ts modules — runes work identically there
16
+ - Fetch secrets and DB data in +page.server.ts — +page.ts code ships to the browser
17
+ - Fetch shared layout data in +layout.server.ts — never duplicate it in page loaders
18
+ - Return slow data as unresolved promises from load functions to enable streaming
19
+ - Always use use:enhance on forms for progressive enhancement
20
+ - Return field-keyed errors with fail() from form actions for inline validation
21
+ - Order every component as: <script> → markup → <style>
22
+ - Always use <script lang="ts"> for TypeScript in components
23
+ - Use snippets ({#snippet} / {@render}) for composition — avoid the legacy <slot> API
24
+ - Always key {#each} blocks with a unique ID — never use array index as a key
25
+ - Lazy load heavy components that appear below the fold or on interaction
26
+ - Never use document.querySelector inside components — use bind:this or Svelte actions
27
+ - Create +error.svelte pages for every route group that needs a custom error UI
28
+ - Implement handleError in hooks.server.ts to sanitize errors and send them to monitoring
29
+ - Run svelte-check in CI to catch type errors across all .svelte files before merging
15
30
 
16
31
  folders:
17
- - src/lib/components/ui
18
- - src/lib/components/features
19
- - src/lib/stores
20
- - src/lib/utils
21
- - src/lib/types
22
- - src/routes
32
+ - src/routes/
33
+ - src/routes/+layout.svelte
34
+ - src/routes/+layout.server.ts
35
+ - src/routes/+page.svelte
36
+ - src/routes/+page.server.ts
37
+ - src/routes/+error.svelte
38
+ - src/lib/components/
39
+ - src/lib/components/ui/
40
+ - src/lib/components/features/
41
+ - src/lib/state/
42
+ - src/lib/utils/
43
+ - src/lib/types/
44
+ - src/hooks.server.ts
23
45
 
24
46
  avoid:
25
- - Legacy reactive declarations ($:) in new Svelte 5 code — use runes
47
+ - Updating $state inside $effect (creates reactive loops)
48
+ - Using $effect for derived/computed values — use $derived instead
49
+ - export let syntax in Svelte 5 runes mode
50
+ - Returning writable stores from load functions (breaks runes compatibility and SSR safety)
26
51
  - Fetching data in onMount for server-rendered pages — use load functions
27
- - Global unscoped styles — use scoped <style> blocks
28
- - Two-way binding on complex objects keep data flow predictable
29
- - Large components with mixed responsibilities
52
+ - document.querySelector inside components — use bind:this or Svelte actions
53
+ - Positive tabindex values (breaks keyboard navigation order)
54
+ - Mixing Svelte 4 reactive statements ($:) with Svelte 5 runes
55
+ - Awaiting all slow data in load functions instead of streaming via returned promises
56
+ - Secrets or database logic in +page.ts (ships to the browser)
57
+ - Index as key in {#each} blocks for reorderable or filterable lists