Files
breakpilot-compliance/AGENTS.typescript.md
Sharang Parnerkar 71a4a3d7f3
Some checks failed
CI/CD / loc-budget (push) Successful in 22s
CI/CD / guardrail-integrity (push) Has been skipped
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 43s
CI/CD / test-python-backend-compliance (push) Failing after 38s
CI/CD / test-python-document-crawler (push) Successful in 29s
CI/CD / test-python-dsms-gateway (push) Successful in 28s
CI/CD / sbom-scan (push) Has been skipped
CI/CD / validate-canonical-controls (push) Successful in 16s
docs(agents): require build + lint + test locally before pushing [guardrail-change]
CI was failing on admin-compliance build. Adds mandatory pre-push
checklist to AGENTS.typescript.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:38:21 +02:00

6.1 KiB

AGENTS.typescript.md — TypeScript / Next.js Conventions

Applies to: admin-compliance/, developer-portal/, breakpilot-compliance-sdk/, consent-sdk/, dsms-node/ (where applicable).

Layered architecture (Next.js 15 App Router)

app/
├── <route>/
│   ├── page.tsx              # Server Component by default. ≤200 LOC.
│   ├── layout.tsx
│   ├── _components/          # Private folder; not routable. Colocated UI.
│   │   └── <Component>.tsx   # Each file ≤300 LOC.
│   ├── _hooks/               # Client hooks for this route.
│   ├── _server/              # Server actions, data loaders for this route.
│   └── loading.tsx / error.tsx
├── api/
│   └── <domain>/route.ts     # Thin handler. Delegates to lib/server/<domain>/.
lib/
├── <domain>/                 # Pure helpers, types, schemas (zod). Reusable.
└── server/<domain>/          # Server-only logic; uses "server-only" import.
components/                   # Truly shared, app-wide components.

Server vs Client: Default is Server Component. Add "use client" only when you need state, effects, or browser APIs. Push the boundary as deep as possible.

API routes (route.ts)

  • One handler per HTTP method, ≤40 LOC.
  • Validate input with zod safeParse — never parse (throws and bypasses error handling).
  • Delegate to lib/server/<domain>/. No business logic in route.ts.
  • Always return NextResponse.json(..., { status }). Let the framework's error boundary handle unexpected errors — don't wrap the entire handler in try/catch.
// app/api/<domain>/route.ts  (≤40 LOC)
import { NextRequest, NextResponse } from 'next/server';
import { mySchema } from '@/lib/schemas/<domain>';
import { myService } from '@/lib/server/<domain>';

export async function POST(req: NextRequest) {
  const body = mySchema.safeParse(await req.json());
  if (!body.success) return NextResponse.json({ error: body.error }, { status: 400 });
  const result = await myService.create(body.data);
  return NextResponse.json(result, { status: 201 });
}

Page components

  • Pages >300 lines must be split into colocated _components/.
  • Server Components fetch data; pass plain objects to Client Components.
  • No data fetching in useEffect for server-renderable data.
  • State management: prefer URL state (searchParams) and Server Components over global stores.

Types

  • lib/sdk/types.ts is being split into lib/sdk/types/<domain>.ts. Mirror backend domain boundaries.
  • All API DTOs are zod schemas; infer types via z.infer.
  • No any. No as unknown as. If you reach for it, the type is wrong.
  • Always use import type { Foo } for type-only imports.
  • Never use as type assertions except when bridging external data at a boundary (add a comment explaining why).
  • No @ts-ignore. @ts-expect-error only with a comment explaining the suppression.

Barrel re-export pattern

lib/sdk/types.ts is a barrel — it re-exports from domain-specific files. Do not add new types directly to it.

// lib/sdk/types.ts  (barrel — DO NOT ADD NEW TYPES HERE)
export * from './types/enums';
export * from './types/company-profile';
// ... etc.

// New types go in lib/sdk/types/<domain>.ts
  • When splitting an oversized file, keep the original as a thin barrel so existing imports don't break.
  • New code imports directly from the specific module (e.g. import type { CompanyProfile } from '@/lib/sdk/types/company-profile'), not the barrel.

Server vs Client components

Default is Server Component. Add "use client" only when required:

Need Pattern
Data fetching only Server Component (no directive)
useState / useEffect Client Component ("use client")
Browser API Client Component
Event handlers Client Component
  • Pass only serializable props from Server → Client Components (no functions, no class instances).
  • Never add "use client" to a layout or page just because one child needs it — extract the client part into a _components/ file.

Tests

  • Unit: Vitest (*.test.ts/*.test.tsx), colocated.
  • Hooks: @testing-library/react renderHook.
  • E2E: Playwright (tests/e2e/), one spec per top-level page, smoke happy path minimum.
  • Snapshot tests sparingly — only for stable output (CSV, JSON-LD).
  • Coverage target: 70% on lib/, smoke coverage on app/.

Tooling

  • tsc --noEmit clean (strict mode, noUncheckedIndexedAccess: true).
  • ESLint with @typescript-eslint, eslint-config-next, type-aware rules on.
  • prettier.
  • next build clean. No // @ts-ignore. // @ts-expect-error only with a comment explaining why.

Before every push — MANDATORY

Run all three steps for every affected service (admin-compliance/, developer-portal/) before pushing. CI runs the same checks and will fail if you skip this.

cd admin-compliance   # or developer-portal

# 1. Build — catches type errors and module resolution failures
npm run build

# 2. Lint
npx tsc --noEmit
npx eslint . --max-warnings 0

# 3. Tests
npm test

All three must exit 0. Do not push if any step fails.

Performance

  • Use next/dynamic for heavy client-only components.
  • Image: next/image with explicit width/height.
  • Avoid waterfalls — Promise.all for parallel data fetches in Server Components.

What you may NOT do

  • Put business logic in a page.tsx or route.ts.
  • Reach across module boundaries (e.g. admin-compliance importing from developer-portal).
  • Use dangerouslySetInnerHTML without DOMPurify sanitization.
  • Call internal backend APIs directly from Client Components — use Server Components or API routes as a proxy.
  • Add "use client" to a layout or page just because one child needs it — extract the client part.
  • Spread ...props onto a DOM element without filtering the props first (type error risk).
  • Change a public API route's path/method/schema without updating SDK consumers in the same change.
  • Create a file >500 lines.
  • Disable a lint or type rule globally to silence a finding — fix the root cause.