@eltonssouza/development-utility-kit 0.10.0

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.
Files changed (131) hide show
  1. package/.claude/agents/README.md +24 -0
  2. package/.claude/agents/analyst.md +198 -0
  3. package/.claude/agents/backend-developer.md +126 -0
  4. package/.claude/agents/brain-keeper.md +229 -0
  5. package/.claude/agents/code-reviewer.md +181 -0
  6. package/.claude/agents/database-engineer.md +94 -0
  7. package/.claude/agents/devops-engineer.md +141 -0
  8. package/.claude/agents/frontend-developer.md +97 -0
  9. package/.claude/agents/gate-keeper.md +118 -0
  10. package/.claude/agents/migrator.md +291 -0
  11. package/.claude/agents/mobile-developer.md +80 -0
  12. package/.claude/agents/n8n-specialist.md +94 -0
  13. package/.claude/agents/product-owner.md +115 -0
  14. package/.claude/agents/qa-engineer.md +232 -0
  15. package/.claude/agents/release-engineer.md +204 -0
  16. package/.claude/agents/scaffold.md +87 -0
  17. package/.claude/agents/security-engineer.md +199 -0
  18. package/.claude/agents/sprint-runner.md +46 -0
  19. package/.claude/agents/stack-resolver.md +104 -0
  20. package/.claude/agents/tech-lead.md +182 -0
  21. package/.claude/agents/update-template.md +54 -0
  22. package/.claude/agents/ux-designer.md +118 -0
  23. package/.claude/hooks/flow-guard.js +261 -0
  24. package/.claude/hooks/flow-state.js +197 -0
  25. package/.claude/local/CLAUDE.md +71 -0
  26. package/.claude/settings.json +55 -0
  27. package/.claude/skills/README.md +331 -0
  28. package/.claude/skills/active-project/SKILL.md +131 -0
  29. package/.claude/skills/api-integration-test/SKILL.md +84 -0
  30. package/.claude/skills/auto-test-guard/SKILL.md +239 -0
  31. package/.claude/skills/auto-test-guard/resources/backend-tests.md +20 -0
  32. package/.claude/skills/auto-test-guard/resources/e2e-tests.md +24 -0
  33. package/.claude/skills/auto-test-guard/resources/execution-report.md +49 -0
  34. package/.claude/skills/auto-test-guard/resources/frontend-tests.md +18 -0
  35. package/.claude/skills/auto-test-guard/resources/initial-setup.md +108 -0
  36. package/.claude/skills/auto-test-guard/resources/run-suite.md +48 -0
  37. package/.claude/skills/auto-test-guard/resources/senior-gate.md +19 -0
  38. package/.claude/skills/brain-keeper/SKILL.md +62 -0
  39. package/.claude/skills/brain-keeper/obsidian/app.json +9 -0
  40. package/.claude/skills/brain-keeper/obsidian/appearance.json +4 -0
  41. package/.claude/skills/brain-keeper/obsidian/core-plugins.json +20 -0
  42. package/.claude/skills/brain-keeper/obsidian/daily-notes.json +5 -0
  43. package/.claude/skills/brain-keeper/obsidian/graph.json +32 -0
  44. package/.claude/skills/brain-keeper/obsidian/snippets/folder-colors.css +90 -0
  45. package/.claude/skills/brain-keeper/obsidian/templates.json +5 -0
  46. package/.claude/skills/brain-keeper/templates/README.md +51 -0
  47. package/.claude/skills/brain-keeper/templates/adr.md +40 -0
  48. package/.claude/skills/brain-keeper/templates/bug.md +35 -0
  49. package/.claude/skills/brain-keeper/templates/daily.md +38 -0
  50. package/.claude/skills/brain-keeper/templates/feature.md +62 -0
  51. package/.claude/skills/brain-keeper/templates/meeting.md +34 -0
  52. package/.claude/skills/brain-keeper/templates/tech-debt.md +21 -0
  53. package/.claude/skills/caveman/SKILL.md +189 -0
  54. package/.claude/skills/create-stack-pack/SKILL.md +281 -0
  55. package/.claude/skills/grill-me/SKILL.md +80 -0
  56. package/.claude/skills/pair-debug/SKILL.md +288 -0
  57. package/.claude/skills/prd-ready-check/SKILL.md +86 -0
  58. package/.claude/skills/project-manager/SKILL.md +334 -0
  59. package/.claude/skills/quality-standards/SKILL.md +203 -0
  60. package/.claude/skills/quick-feature/SKILL.md +266 -0
  61. package/.claude/skills/run-sprint/SKILL.md +41 -0
  62. package/.claude/skills/scaffold/SKILL.md +60 -0
  63. package/.claude/skills/stack-discovery/SKILL.md +161 -0
  64. package/.claude/skills/test-coverage-auditor/SKILL.md +87 -0
  65. package/.claude/skills/to-issues/SKILL.md +163 -0
  66. package/.claude/skills/to-prd/SKILL.md +130 -0
  67. package/.claude/skills/update-template/SKILL.md +256 -0
  68. package/.claude/stacks/CODEOWNERS +30 -0
  69. package/.claude/stacks/README.md +97 -0
  70. package/.claude/stacks/_template.md +116 -0
  71. package/.claude/stacks/dotnet/aspire-9.md +528 -0
  72. package/.claude/stacks/go/gin-1.10.md +570 -0
  73. package/.claude/stacks/java/spring-boot-3.md +376 -0
  74. package/.claude/stacks/java/spring-boot-4.md +438 -0
  75. package/.claude/stacks/node/express-5.md +538 -0
  76. package/.claude/stacks/python/django-5.md +483 -0
  77. package/.claude/stacks/python/fastapi-0.115.md +522 -0
  78. package/.claude/stacks/typescript/angular-18.md +420 -0
  79. package/.claude/stacks/typescript/angular-19.md +397 -0
  80. package/.claude/stacks/typescript/angular-21.md +494 -0
  81. package/CLAUDE.md +472 -0
  82. package/README.md +412 -0
  83. package/bin/cli.js +848 -0
  84. package/bin/lib/adr.js +146 -0
  85. package/bin/lib/backup.js +62 -0
  86. package/bin/lib/detect-stack.js +476 -0
  87. package/bin/lib/doctor.js +527 -0
  88. package/bin/lib/help.js +328 -0
  89. package/bin/lib/identity.js +108 -0
  90. package/bin/lib/lint-allowlist.json +15 -0
  91. package/bin/lib/lint.js +798 -0
  92. package/bin/lib/local-dir.js +68 -0
  93. package/bin/lib/manifest.js +236 -0
  94. package/bin/lib/sync-all.js +394 -0
  95. package/bin/lib/version-check.js +398 -0
  96. package/dashboard/db.js +321 -0
  97. package/dashboard/package.json +22 -0
  98. package/dashboard/public/app.js +853 -0
  99. package/dashboard/public/content/docs/agents-reference.en.md +911 -0
  100. package/dashboard/public/content/docs/architecture-overview.en.md +252 -0
  101. package/dashboard/public/content/docs/autonomy-matrix.en.md +186 -0
  102. package/dashboard/public/content/docs/cli-reference.en.md +538 -0
  103. package/dashboard/public/content/docs/git-flow.en.md +525 -0
  104. package/dashboard/public/content/docs/honcho-memory.en.md +394 -0
  105. package/dashboard/public/content/docs/hooks-reference.en.md +404 -0
  106. package/dashboard/public/content/docs/pipeline.en.md +414 -0
  107. package/dashboard/public/content/docs/plugins.en.md +289 -0
  108. package/dashboard/public/content/docs/quality-gate.en.md +315 -0
  109. package/dashboard/public/content/docs/skills-reference.en.md +484 -0
  110. package/dashboard/public/content/docs/stack-rules.en.md +362 -0
  111. package/dashboard/public/content/docs/troubleshooting.en.md +565 -0
  112. package/dashboard/public/content/manifest.json +114 -0
  113. package/dashboard/public/content/manual/backend.en.md +1053 -0
  114. package/dashboard/public/content/manual/existing-project.en.md +848 -0
  115. package/dashboard/public/content/manual/frontend.en.md +1008 -0
  116. package/dashboard/public/content/manual/fullstack.en.md +1459 -0
  117. package/dashboard/public/content/manual/mobile.en.md +837 -0
  118. package/dashboard/public/content/manual/quickstart.en.md +169 -0
  119. package/dashboard/public/index.html +217 -0
  120. package/dashboard/public/style.css +857 -0
  121. package/dashboard/public/vendor/marked.min.js +69 -0
  122. package/dashboard/rtk.js +143 -0
  123. package/dashboard/server-app.js +421 -0
  124. package/dashboard/server.js +104 -0
  125. package/dashboard/test/sprint1.test.js +406 -0
  126. package/dashboard/test/sprint2.test.js +571 -0
  127. package/dashboard/test/sprint3.test.js +560 -0
  128. package/package.json +33 -0
  129. package/scripts/hooks/subagent-telemetry.sh +14 -0
  130. package/scripts/hooks/telemetry-writer.js +250 -0
  131. package/scripts/latest-versions.json +56 -0
@@ -0,0 +1,538 @@
1
+ ---
2
+ stack: node/express-5
3
+ versions_covered: "5.0.x — 5.2.x"
4
+ last_validated: 2026-05-28
5
+ validated_against: "reference pack — Node 22 LTS + Express 5.0 + Prisma 5.20 + Jest 29"
6
+ status: active
7
+ pack_owner: "@elton"
8
+ security_review: 2026-05-28
9
+ next_review_due: 2027-05-28
10
+ ---
11
+
12
+ # Node 22+ + Express 5.x
13
+
14
+ Canonical knowledge pack for Node.js HTTP services on Express 5.x. Express 5 (released October 2024, after years of beta) brings async error handling as a first-class behavior, Node 18+ floor, modern route matching, and various breaking changes from Express 4. For new projects that want a heavier opinion (DI, validation, OpenAPI), consider NestJS instead — this pack assumes plain Express with TypeScript and explicit DDD layering.
15
+
16
+ ## 1. When to use this pack
17
+
18
+ - Project declares `Primary stack: Node 22+ + Express 5.x + TypeScript` in `## Project Identity`.
19
+ - `package.json` declares `"express": "^5.0.0"` and `"node": ">=22"`.
20
+ - Service is API-first (REST), small-to-medium throughput, team prefers explicit code over framework magic.
21
+ - For SSR with React/Vue or full framework with DI: Next.js, NestJS, or Fastify+plugins are better fits.
22
+ - For high-throughput edge: Hono / Fastify outperform Express. Use this pack if Express's middleware ecosystem is the deciding factor.
23
+
24
+ ## 2. Stack baseline (what this pack assumes)
25
+
26
+ | Component | Version range | Notes |
27
+ |---|---|---|
28
+ | Node.js | 22 LTS (min) / 24 (latest) | Native `--watch`, native `node:test`, native `fetch` — use them |
29
+ | TypeScript | 5.6.x+ | strict mode mandatory; `noUncheckedIndexedAccess: true` |
30
+ | Express | 5.0.x — 5.2.x | Async route handlers handle errors automatically; major param matching changes from 4.x |
31
+ | ORM | Prisma 5.20+ (recommended) OR TypeORM 0.3.20+ | Prisma's typed client + migrations is the cleanest single-tool option |
32
+ | Validation | Zod 3.23+ | TypeScript-first; infer types from schema; use everywhere |
33
+ | Build | `tsc` for compilation OR `tsx` for dev OR esbuild for prod bundles | `tsx watch` for dev; `tsc --noEmit && esbuild` for prod |
34
+ | Package manager | pnpm 9.x (recommended) OR npm 10.x | pnpm is faster + safer (strict deps) |
35
+ | Tests | Jest 29.x OR Vitest 2.x + Supertest 7.x + Testcontainers Node 10.x | NEVER SQLite if prod is Postgres |
36
+ | Mutation | Stryker 8.x | Target ≥70% on `domain/` + `application/` |
37
+ | Coverage | Jest `--coverage` (V8 provider) | Target ≥85% lines, ≥80% branches |
38
+ | Static analysis | `eslint` v9 (flat config) + `@typescript-eslint` + `prettier` | `--max-warnings 0` |
39
+ | Security scan | `npm audit --omit=dev` + `snyk test` (optional) | 0 CVE with CVSS ≥7.0 |
40
+ | Observability | OpenTelemetry SDK + `@opentelemetry/instrumentation-express` | W3C Trace Context; auto-instruments routes + Prisma + http |
41
+ | Logger | `pino` 9.x | High performance JSON; structured by default |
42
+ | Config | `zod` for env validation at startup | Crash fast on bad env |
43
+ | Process manager | `pm2` or container runtime | Cluster mode only when needed; Node 22 handles concurrency well single-process |
44
+
45
+ ## 3. Project structure (DDD-flavored Express)
46
+
47
+ ```
48
+ service/
49
+ ├── package.json
50
+ ├── pnpm-lock.yaml
51
+ ├── tsconfig.json
52
+ ├── eslint.config.js # flat config
53
+ ├── prisma/
54
+ │ ├── schema.prisma
55
+ │ └── migrations/
56
+ ├── .env.example
57
+ ├── src/
58
+ │ ├── domain/ # pure TS; no Express, no Prisma
59
+ │ │ ├── product/
60
+ │ │ │ ├── entity.ts
61
+ │ │ │ ├── repository.ts # interface
62
+ │ │ │ └── service.ts
63
+ │ │ └── shared/
64
+ │ ├── application/ # use cases (1 class per use case)
65
+ │ │ ├── product/
66
+ │ │ │ ├── createProduct.ts
67
+ │ │ │ ├── listProducts.ts
68
+ │ │ │ └── dto.ts
69
+ │ │ └── shared/
70
+ │ ├── infrastructure/ # Prisma, fetch wrappers
71
+ │ │ ├── db/
72
+ │ │ │ └── prisma.ts # PrismaClient singleton
73
+ │ │ ├── product/
74
+ │ │ │ └── repository.ts # adapter for domain interface
75
+ │ │ └── http/
76
+ │ │ └── upstreamClient.ts
77
+ │ ├── api/ # Express routes + middleware
78
+ │ │ ├── product/
79
+ │ │ │ ├── router.ts
80
+ │ │ │ ├── handlers.ts
81
+ │ │ │ └── schemas.ts # Zod schemas (req/res)
82
+ │ │ └── shared/
83
+ │ │ ├── errorHandler.ts # RFC 9457 ProblemDetails
84
+ │ │ └── middleware.ts
85
+ │ ├── config/
86
+ │ │ └── env.ts # Zod-validated env
87
+ │ └── server.ts # Express app + listen
88
+ └── tests/
89
+ ├── unit/
90
+ ├── integration/
91
+ └── e2e/
92
+ ```
93
+
94
+ **Rule**: `domain/` and `application/` contain **zero Express and zero Prisma imports**. They are pure TS testable without HTTP or DB. `infrastructure/` is the only layer importing `@prisma/client`. `api/` is the only layer importing `express`.
95
+
96
+ ## 4. Code patterns
97
+
98
+ ### Domain entity (no Prisma, no Express)
99
+
100
+ ```typescript
101
+ // src/domain/product/entity.ts
102
+ export class Product {
103
+ constructor(
104
+ public readonly id: string, // UUID
105
+ public readonly name: string,
106
+ public readonly priceCents: bigint, // bigint for money — never number
107
+ public readonly stock: number,
108
+ public readonly createdAt: Date,
109
+ ) {}
110
+
111
+ reserve(qty: number): Product {
112
+ if (qty <= 0) throw new Error("qty must be positive");
113
+ if (qty > this.stock) throw new Error("insufficient stock");
114
+ return new Product(this.id, this.name, this.priceCents, this.stock - qty, this.createdAt);
115
+ }
116
+ }
117
+ ```
118
+
119
+ **Rule**: money in `bigint` (or `Decimal` from prisma) — never `number` (IEEE 754 loses precision). Domain methods take and return value types. Immutability by constructor + `readonly`.
120
+
121
+ ### Domain port + infrastructure adapter (Prisma)
122
+
123
+ ```typescript
124
+ // src/domain/product/repository.ts
125
+ import type { Product } from "./entity";
126
+
127
+ export interface ProductRepository {
128
+ get(id: string): Promise<Product | null>;
129
+ save(product: Product): Promise<void>;
130
+ }
131
+ ```
132
+
133
+ ```typescript
134
+ // src/infrastructure/product/repository.ts
135
+ import type { PrismaClient, Product as ProductRow } from "@prisma/client";
136
+ import type { ProductRepository } from "@/domain/product/repository";
137
+ import { Product } from "@/domain/product/entity";
138
+
139
+ export class PrismaProductRepository implements ProductRepository {
140
+ constructor(private readonly prisma: PrismaClient) {}
141
+
142
+ async get(id: string): Promise<Product | null> {
143
+ const row = await this.prisma.product.findUnique({ where: { id } });
144
+ return row ? this.toDomain(row) : null;
145
+ }
146
+
147
+ async save(p: Product): Promise<void> {
148
+ await this.prisma.product.upsert({
149
+ where: { id: p.id },
150
+ create: { id: p.id, name: p.name, priceCents: p.priceCents, stock: p.stock },
151
+ update: { name: p.name, priceCents: p.priceCents, stock: p.stock },
152
+ });
153
+ }
154
+
155
+ private toDomain(row: ProductRow): Product {
156
+ return new Product(row.id, row.name, row.priceCents, row.stock, row.createdAt);
157
+ }
158
+ }
159
+ ```
160
+
161
+ **Rule**: domain `ProductRepository` is an interface in `domain/`. Implementation imports Prisma. Use cases depend on the interface. UUID primary key.
162
+
163
+ ### Zod schemas + handler (Express 5 async routes)
164
+
165
+ ```typescript
166
+ // src/api/product/schemas.ts
167
+ import { z } from "zod";
168
+
169
+ export const createProductRequest = z.object({
170
+ name: z.string().min(1).max(120),
171
+ priceCents: z.coerce.bigint().positive(),
172
+ stock: z.number().int().min(0),
173
+ }).strict(); // strict() rejects unknown keys
174
+
175
+ export type CreateProductRequest = z.infer<typeof createProductRequest>;
176
+
177
+ export const productResponse = z.object({
178
+ id: z.string().uuid(),
179
+ name: z.string(),
180
+ priceCents: z.bigint(),
181
+ stock: z.number(),
182
+ createdAt: z.date(),
183
+ });
184
+
185
+ export type ProductResponse = z.infer<typeof productResponse>;
186
+ ```
187
+
188
+ ```typescript
189
+ // src/api/product/handlers.ts
190
+ import type { Request, Response } from "express";
191
+ import { createProductRequest } from "./schemas";
192
+ import type { CreateProductUseCase } from "@/application/product/createProduct";
193
+
194
+ export class ProductHandlers {
195
+ constructor(private readonly createUC: CreateProductUseCase) {}
196
+
197
+ // Express 5: async errors propagate to error middleware automatically
198
+ create = async (req: Request, res: Response) => {
199
+ const parsed = createProductRequest.parse(req.body); // throws ZodError on invalid
200
+ const product = await this.createUC.execute(parsed);
201
+ res.status(201).json({
202
+ id: product.id,
203
+ name: product.name,
204
+ priceCents: product.priceCents.toString(),
205
+ stock: product.stock,
206
+ createdAt: product.createdAt,
207
+ });
208
+ };
209
+ }
210
+ ```
211
+
212
+ **Rule**: Zod schemas are the API contract. `parse()` throws on invalid; error middleware catches and returns RFC 9457. `strict()` rejects unknown keys (silent acceptance = footgun).
213
+
214
+ ### Router + middleware chain
215
+
216
+ ```typescript
217
+ // src/api/product/router.ts
218
+ import { Router } from "express";
219
+ import type { ProductHandlers } from "./handlers";
220
+
221
+ export function productRouter(h: ProductHandlers): Router {
222
+ const r = Router();
223
+ r.post("/", h.create);
224
+ return r;
225
+ }
226
+ ```
227
+
228
+ ```typescript
229
+ // src/server.ts
230
+ import express from "express";
231
+ import helmet from "helmet";
232
+ import compression from "compression";
233
+ import cors from "cors";
234
+ import pinoHttp from "pino-http";
235
+ import { productRouter } from "@/api/product/router";
236
+ import { errorHandler } from "@/api/shared/errorHandler";
237
+ import { config } from "@/config/env";
238
+
239
+ const app = express();
240
+ app.disable("x-powered-by");
241
+ app.use(helmet());
242
+ app.use(compression());
243
+ app.use(cors({ origin: config.CORS_ORIGINS, credentials: true }));
244
+ app.use(express.json({ limit: "100kb" }));
245
+ app.use(pinoHttp({ logger }));
246
+
247
+ app.use("/api/v1/products", productRouter(deps.productHandlers));
248
+
249
+ // Centralized error handler — Express 5 catches async errors here
250
+ app.use(errorHandler);
251
+
252
+ app.listen(config.PORT, () => {
253
+ logger.info({ port: config.PORT }, "server up");
254
+ });
255
+ ```
256
+
257
+ **Rule**: `helmet()` before any routes. `express.json({ limit: "100kb" })` always — no default = DoS via large payload. `app.disable("x-powered-by")` — small but free hardening.
258
+
259
+ ### Error handler (RFC 9457 ProblemDetails)
260
+
261
+ ```typescript
262
+ // src/api/shared/errorHandler.ts
263
+ import type { ErrorRequestHandler } from "express";
264
+ import { ZodError } from "zod";
265
+
266
+ export const errorHandler: ErrorRequestHandler = (err, req, res, _next) => {
267
+ if (err instanceof ZodError) {
268
+ res.status(422).json({
269
+ type: "https://example.com/errors/validation",
270
+ title: "Validation failed",
271
+ status: 422,
272
+ instance: req.originalUrl,
273
+ errors: err.errors,
274
+ });
275
+ return;
276
+ }
277
+ // log full error server-side
278
+ req.log?.error({ err }, "unhandled error");
279
+ res.status(500).json({
280
+ type: "about:blank",
281
+ title: "Internal Server Error",
282
+ status: 500,
283
+ instance: req.originalUrl,
284
+ });
285
+ };
286
+ ```
287
+
288
+ **Rule**: error handler must be the LAST middleware. Express 5 routes catch async errors and pass to this middleware automatically (huge improvement over Express 4 where every async route needed try/catch or `express-async-errors`).
289
+
290
+ ## 5. Testing
291
+
292
+ ### Unit (Jest, no Express/DB)
293
+
294
+ ```typescript
295
+ // src/domain/product/entity.test.ts
296
+ import { Product } from "./entity";
297
+
298
+ describe("Product.reserve", () => {
299
+ const make = (stock: number) =>
300
+ new Product("id-1", "x", 990n, stock, new Date());
301
+
302
+ it("decreases stock", () => {
303
+ expect(make(10).reserve(3).stock).toBe(7);
304
+ });
305
+
306
+ it("refuses zero", () => {
307
+ expect(() => make(10).reserve(0)).toThrow();
308
+ });
309
+
310
+ it("refuses excess", () => {
311
+ expect(() => make(10).reserve(11)).toThrow();
312
+ });
313
+ });
314
+ ```
315
+
316
+ ### Integration (Supertest + Testcontainers Postgres)
317
+
318
+ ```typescript
319
+ // tests/integration/product.test.ts
320
+ import request from "supertest";
321
+ import { PostgreSqlContainer } from "@testcontainers/postgresql";
322
+ import { buildApp } from "@/server";
323
+
324
+ describe("POST /api/v1/products", () => {
325
+ let pg: any;
326
+ let app: any;
327
+
328
+ beforeAll(async () => {
329
+ pg = await new PostgreSqlContainer("postgres:16-alpine").start();
330
+ process.env.DATABASE_URL = pg.getConnectionUri();
331
+ app = await buildApp();
332
+ });
333
+
334
+ afterAll(async () => {
335
+ await pg.stop();
336
+ });
337
+
338
+ it("creates a product", async () => {
339
+ const r = await request(app)
340
+ .post("/api/v1/products")
341
+ .send({ name: "widget", priceCents: "990", stock: 100 });
342
+ expect(r.status).toBe(201);
343
+ expect(r.body.name).toBe("widget");
344
+ });
345
+ });
346
+ ```
347
+
348
+ **Rule**: Testcontainers Postgres for integration tests. NEVER SQLite if prod is Postgres.
349
+
350
+ ### Mutation (Stryker)
351
+
352
+ ```bash
353
+ npx stryker run
354
+ # stryker.conf.mjs targets src/domain and src/application
355
+ # Target: mutation score >= 70%
356
+ ```
357
+
358
+ ## 6. Build & run commands
359
+
360
+ ```bash
361
+ # Setup
362
+ pnpm install
363
+
364
+ # Generate Prisma client (after schema change)
365
+ pnpm prisma generate
366
+
367
+ # Migrations
368
+ pnpm prisma migrate dev --name add_products # dev: creates + applies
369
+ pnpm prisma migrate deploy # prod: only applies
370
+
371
+ # Run dev (with auto-reload via tsx)
372
+ pnpm dev # tsx watch src/server.ts
373
+
374
+ # Run prod (after build)
375
+ pnpm build # tsc → dist/
376
+ pnpm start # node dist/server.js
377
+
378
+ # Tests
379
+ pnpm test # jest
380
+ pnpm test:unit # unit only
381
+ pnpm test --coverage # with coverage
382
+ pnpm test:watch
383
+
384
+ # Mutation
385
+ pnpm stryker run
386
+
387
+ # Lint + format
388
+ pnpm lint # eslint
389
+ pnpm format # prettier --write
390
+ pnpm typecheck # tsc --noEmit
391
+
392
+ # Security scan
393
+ pnpm audit --omit=dev
394
+ pnpm dlx snyk test # optional, if Snyk account
395
+ ```
396
+
397
+ ## 7. Security (per ADR-007 + ADR-027 — MANDATORY section)
398
+
399
+ ### 7.1 Authentication & Authorization
400
+
401
+ - **JWT**: `jsonwebtoken` 9.x. RS256 preferred multi-service; HS256 single-service. Validate `iss`, `aud`, `exp`. `kid` rotation.
402
+ - **OAuth2 / OIDC**: `openid-client` or hosted IdP (Auth0 / Keycloak / Cognito).
403
+ - **Sessions**: `express-session` + Redis store. Secure cookie flags: `httpOnly`, `secure`, `sameSite: "lax"`.
404
+ - **Password hashing**: `bcrypt` cost 12 minimum OR `argon2` (preferred). NEVER plain `crypto.createHash("sha256")`.
405
+ - **API keys**: `crypto.randomBytes(32).toString("base64url")`; hash before storage.
406
+ - **Authorization**: middleware that decodes JWT, attaches `req.user`, then per-route role check. Object-level checks inside the use case.
407
+
408
+ ### 7.2 CORS
409
+
410
+ ```typescript
411
+ app.use(cors({
412
+ origin: ["https://app.example.com"], // NEVER "*" in prod
413
+ credentials: true,
414
+ methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
415
+ allowedHeaders: ["Authorization", "Content-Type"],
416
+ }));
417
+ ```
418
+
419
+ ### 7.3 Validation & input sanitization
420
+
421
+ - **SQL injection**: Prisma parameterizes everything. NEVER `$queryRawUnsafe` with template strings; use `$queryRaw` with the tagged template literal (Prisma parameterizes).
422
+ - **NoSQL injection**: if using MongoDB, never accept raw `$where`/`$regex` from user input.
423
+ - **Zod everywhere**: every route body, query, params validated. `.strict()` to reject unknown keys.
424
+ - **JSON body limit**: `express.json({ limit: "100kb" })` — default is 100kb, but be explicit. Larger uploads should be multipart.
425
+ - **Path traversal**: `path.resolve` + check inside an allowed base before opening any file.
426
+ - **Prototype pollution**: never `Object.assign(target, JSON.parse(userInput))` — Zod schemas prevent this naturally.
427
+
428
+ ### 7.4 Secrets management
429
+
430
+ ```typescript
431
+ // src/config/env.ts
432
+ import { z } from "zod";
433
+
434
+ const envSchema = z.object({
435
+ NODE_ENV: z.enum(["development", "test", "production"]),
436
+ PORT: z.coerce.number().int().positive().default(3000),
437
+ DATABASE_URL: z.string().url(),
438
+ JWT_SECRET: z.string().min(32),
439
+ CORS_ORIGINS: z.string().transform((s) => s.split(",")),
440
+ });
441
+
442
+ export const config = envSchema.parse(process.env); // throws on bad env
443
+ ```
444
+
445
+ - `.env` gitignored; `.env.example` committed.
446
+ - Crash on missing/invalid env at startup. Never `process.env.FOO ?? "fallback"` — silent fallback masks misconfiguration.
447
+ - Prefer Secrets Manager (AWS / GCP / Vault) in prod.
448
+
449
+ ### 7.5 Rate limiting
450
+
451
+ ```typescript
452
+ import rateLimit from "express-rate-limit";
453
+
454
+ const authLimiter = rateLimit({
455
+ windowMs: 60_000,
456
+ max: 5,
457
+ standardHeaders: true,
458
+ legacyHeaders: false,
459
+ });
460
+
461
+ app.use("/api/v1/auth", authLimiter);
462
+ ```
463
+
464
+ - `5/min` on `/auth/login`, `/auth/signup`, `/auth/password-reset`.
465
+ - `100/min` on regular GETs.
466
+ - Behind reverse proxy: `app.set("trust proxy", 1)` and `keyGenerator: (req) => req.ip` carefully — only trust the proxy IP.
467
+
468
+ ### 7.6 OWASP Top 10 mapping
469
+
470
+ | OWASP | Mitigation in Node 22 + Express 5 |
471
+ |---|---|
472
+ | A01 Broken Access Control | JWT middleware + per-route role check; object-level check in use case; default-deny |
473
+ | A02 Cryptographic Failures | bcrypt cost 12 or argon2; TLS at proxy; HSTS via `helmet({ hsts: { maxAge: 31536000 } })`; JWT RS256 with key rotation |
474
+ | A03 Injection | Prisma parameterization; Zod validation on every input; never `$queryRawUnsafe` |
475
+ | A04 Insecure Design | DDD layering enforced (`domain/` no Express/Prisma); use case = single async method per class; ADRs document deviations |
476
+ | A05 Security Misconfiguration | `helmet()`; `app.disable("x-powered-by")`; `express.json({ limit: "100kb" })`; CORS allowlist explicit; `NODE_ENV=production` |
477
+ | A06 Vulnerable Components | `pnpm audit` in CI; `renovate` for `package.json` bumps; lockfile committed |
478
+ | A07 Auth Failures | rate-limit on auth endpoints; argon2/bcrypt; account lock via Redis counter; MFA via `otplib` |
479
+ | A08 Data Integrity | `pnpm install --frozen-lockfile` in CI; SBOM via `cyclonedx-npm`; package-lock signature verification on critical deps |
480
+ | A09 Logging Failures | `pino` structured JSON; correlation ID via `cls-rtracer` or AsyncLocalStorage; **NEVER** log `req.body` raw (PII) |
481
+ | A10 SSRF | Centralized fetch wrapper with allowlist of outbound hosts; reject `localhost`, link-local (`169.254.0.0/16`), private CIDRs by default |
482
+
483
+ ### 7.7 LGPD / GDPR / compliance specifics
484
+
485
+ - **PII tagging**: convention via Prisma comments (`/// @pii`) + a custom lint that checks string fields named Email/CPF/Document have it.
486
+ - **Soft delete**: Prisma middleware that intercepts `delete` and rewrites as `update { deletedAt: new Date() }`. NEVER hard delete user-owned data.
487
+ - **Data subject access (Art 15)**: `GET /api/v1/me/export` returns user data as ZIP.
488
+ - **Erasure (Art 17)**: `DELETE /api/v1/me` redacts PII fields while preserving FKs.
489
+ - **Encryption at rest**: PostgreSQL TDE (cloud-managed) or column-level via `prisma-field-encryption` for sensitive fields.
490
+
491
+ ## 8. Anti-patterns (block in code-review)
492
+
493
+ | ❌ Bad | ✅ Good | Why |
494
+ |---|---|---|
495
+ | `app.use(express.json())` without `limit` | `app.use(express.json({ limit: "100kb" }))` | Default works but explicit is safer; large limit = DoS |
496
+ | Async route without error handling in Express 4 style (`(req, res) => { foo().then(...) }`) | Express 5 async route — automatic error forwarding | Express 5 handles it; manual try/catch noise removed |
497
+ | `number` for money | `bigint` (cents) or Prisma `Decimal` | IEEE 754 precision loss is real |
498
+ | `process.env.FOO ?? "fallback"` | Zod-validated env, crash on missing | Silent fallback masks misconfig |
499
+ | Returning Prisma row directly from handler | DTO with explicit serialization | API contract leaks data model |
500
+ | `$queryRawUnsafe(`...${userInput}...`)` | `$queryRaw\`...${userInput}...\`` (tagged template) | Prisma parameterizes only the tagged version |
501
+ | `Object.assign(user, req.body)` | Zod parse + explicit field copy | Prototype pollution + mass assignment |
502
+ | `JSON.stringify(err)` in error response | RFC 9457 ProblemDetails with controlled fields | Leaks stack traces / DB internals to client |
503
+ | `console.log` in prod | `pino` structured JSON logger | console.log loses metadata + slow |
504
+ | `setTimeout(() => doWork(), 0)` instead of `setImmediate` or worker thread | Worker threads or queue for CPU-bound work | Event loop blocking under load |
505
+ | `Buffer.from(input, "base64")` without validation | Validate input is base64 first; catch + 400 | Malformed input crashes or returns garbage |
506
+ | Default `eslint` config without strict TS | `@typescript-eslint` strict + `--max-warnings 0` | Type safety must be enforced |
507
+
508
+ ## 9. Migration hints — Express 4 → 5
509
+
510
+ Breaking changes worth flagging when `migrator` agent runs Express 4 → 5:
511
+
512
+ - **Node 18 minimum** (Express 5); recommend Node 22 LTS.
513
+ - **Async error handling**: async route handlers that throw now reach the error middleware automatically. Remove `express-async-errors` package and any manual `next(err)` in catch blocks — they still work but are redundant.
514
+ - **`req.param()` removed**: use `req.params.id` / `req.query.id` / `req.body.id` explicitly.
515
+ - **`res.redirect("back")` removed**: use `res.redirect(req.get("Referrer") || "/")`.
516
+ - **Path matching changed**: regex routes need re-validation. `:param?` optional syntax may behave differently. Test all routes.
517
+ - **`path-to-regexp` v8**: stricter parsing. Routes like `/user/:id(\\d+)` may need adjustment.
518
+ - **`req.query` is now `null` if empty** (vs `{}` in v4). Defensive access: `req.query?.foo`.
519
+ - **Body parsers**: `express.json` and `express.urlencoded` no longer accept charsets via `type` option without explicit configuration.
520
+ - **`res.json` returns `this`**: chainable, but `await res.json(...)` may behave subtly differently if you depended on the old void return.
521
+
522
+ Hand off to `migrator` with: current Express version, route count, list of routes using regex patterns, list of middleware using deprecated APIs.
523
+
524
+ ## 10. References
525
+
526
+ - [Express 5 migration guide](https://expressjs.com/en/guide/migrating-5.html)
527
+ - [Express docs](https://expressjs.com/en/api.html)
528
+ - [Prisma docs](https://www.prisma.io/docs)
529
+ - [Zod docs](https://zod.dev/)
530
+ - [pino](https://github.com/pinojs/pino)
531
+ - [helmet docs](https://helmetjs.github.io/)
532
+ - [Testcontainers Node](https://node.testcontainers.org/)
533
+ - [Stryker mutation](https://stryker-mutator.io/docs/stryker-js/introduction/)
534
+ - [OWASP Node.js Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html)
535
+ - ADR-007 (Senior+ gate thresholds — coverage ≥85%, mutation ≥70%)
536
+ - ADR-026 (Generic agents + stack packs architecture)
537
+ - ADR-027 (Pack governance — frontmatter + security mandatory + CODEOWNERS + annual review)
538
+ - ADR-029 (Canonical pack format — this document follows it)