AgentStack
SKILL verified MIT Self-run

Typescript Strict

skill-0xmassi-claude-skills-typescript-strict · by 0xMassi

>

No reviews yet
0 installs
11 views
0.0% view→install

Install

$ agentstack add skill-0xmassi-claude-skills-typescript-strict

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Typescript Strict? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

TypeScript Strict Standard

Rules extracted from 10+ production TypeScript codebases. All projects use strict: true.

CRITICAL: Type Safety Rules

TS-01: Never use any

// BAD
function process(data: any) { return data.value; }
const handler = (e: any) => console.log(e);

// GOOD
function process(data: Record) { return "value" in data ? data.value : undefined; }
const handler = (e: MouseEvent) => console.log(e.target);

// ACCEPTABLE: Library integration boundaries (shadcn/ui props, Tauri responses)
// Must add a comment explaining why
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- CodeMirror extension type

Exceptions (document each with a comment):

  • Third-party library props that genuinely require any
  • Generic catch blocks: catch (e: unknown) then narrow

TS-02: Avoid as type assertions

// BAD
const user = response as User;
const el = document.getElementById("root") as HTMLDivElement;

// GOOD: use type guards
function isUser(data: unknown): data is User {
  return typeof data === "object" && data !== null && "id" in data;
}
if (isUser(response)) { /* response is User here */ }

// GOOD: use generics
const el = document.getElementById("root");
if (el instanceof HTMLDivElement) { /* el is HTMLDivElement here */ }

// ACCEPTABLE (rare): When you KNOW the type and can't prove it
// Must be `as unknown as T` with a SAFETY comment
const validated = session as unknown as AuthenticatedSession; // SAFETY: verified user.id exists above

When as is acceptable:

  • Prisma generated types requiring cast at boundaries
  • After runtime validation (Zod parse → safe to assert)
  • Event target narrowing after guard: e.target as Node after if (e.target)

TS-03: Use unknown over any for untyped data

// BAD
function parse(input: any): Config { ... }

// GOOD
function parse(input: unknown): Config {
  if (typeof input !== "object" || input === null) throw new Error("Invalid input");
  // Narrow from unknown
}

TS-04: Never use @ts-ignore

// BAD
// @ts-ignore
const x = brokenLibrary.method();

// GOOD: use @ts-expect-error with explanation (if truly needed)
// @ts-expect-error: library v3 types missing this method, fixed in v4
const x = brokenLibrary.method();

Target: zero @ts-ignore across the project.

TS-05: Minimize non-null assertions (!)

// BAD
const name = user!.name;

// GOOD
if (!user) throw new Error("User required");
const name = user.name;

// ACCEPTABLE: After a guard that guarantees existence
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
  // menuRef.current is guaranteed non-null here
}

Target: under 10 non-null assertions per project, each justified by a guard above.

HIGH: Clean Code Rules

TS-06: Use type-only imports

// BAD
import { Agent, AgentSession } from "@/context/AppContext";

// GOOD: when only used as types
import type { Agent, AgentSession } from "@/context/AppContext";
import { useApp } from "@/context/AppContext"; // Value import separate

Reduces bundle size and makes dependency intent clear.

TS-07: Use discriminated unions over boolean flags

// BAD
type Status = { loading: boolean; error: boolean; data?: User };

// GOOD
type Status =
  | { state: "idle" }
  | { state: "loading" }
  | { state: "error"; message: string }
  | { state: "success"; data: User };

TS-08: Prefer type over interface (unless extending)

// GOOD: type for unions, intersections, mapped types
type Page = "workspace" | "github" | "settings";
type Agent = "claude" | "codex" | "opencode";
type Props = { title: string; onClose: () => void };

// GOOD: interface when you need declaration merging or extends
interface AgentNodeData extends Record {
  role: AgentRole;
  isEnabled: boolean;
}

Default: type for definitions, interface only when you need extends/declaration merging.

TS-09: Path aliases always

// BAD
import { Button } from "../../../components/ui/button";

// GOOD
import { Button } from "@/components/ui/button";

Configure in tsconfig.json:

{ "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }

TS-10: No React.FC: use direct function declarations

// BAD
const PostIt: React.FC = ({ folder, onSave }) => { ... };

// GOOD
export default function PostIt({ folder, onSave }: PostItProps) { ... }

// Also GOOD: arrow function with explicit props type
export const PostIt = ({ folder, onSave }: PostItProps) => { ... };

Avoid React.FC entirely.

HIGH: Error Handling Rules

TS-11: Structured error handling with Result pattern or Zod

// Pattern A: Discriminated union Result
type AuthResult =
  | { session: AuthenticatedSession; error: null }
  | { session: null; error: NextResponse };

async function requireAuth(): Promise {
  const session = await auth();
  if (!session) return { session: null, error: unauthorized() };
  return { session, error: null };
}

// Pattern B: Zod validation at boundaries
const parsed = serverEnvSchema.safeParse(process.env);
if (!parsed.success) {
  throw new Error(`Invalid env:\n${parsed.error.format()}`);
}

TS-12: Catch blocks must narrow error type

// BAD
try { ... } catch (err) { console.log(err.message); }

// GOOD
try { ... } catch (err) {
  const message = err instanceof Error ? err.message : String(err);
  toast.error(`Failed: ${message}`);
}

TS-13: Never swallow errors silently

// BAD
try { await save(); } catch {}

// GOOD
try { await save(); } catch (err) {
  console.error("Save failed:", err);
  // At minimum log, ideally surface to user
}

MEDIUM: React-Specific Rules

TS-14: Event listener cleanup

useEffect(() => {
  const handler = (e: KeyboardEvent) => { ... };
  window.addEventListener("keydown", handler);
  return () => window.removeEventListener("keydown", handler);
}, []);

TS-15: Tauri event listener cleanup

useEffect(() => {
  let unlisten: (() => void) | undefined;
  listen("pty://output", (event) => {
    // handle event
  }).then(fn => { unlisten = fn; });
  return () => unlisten?.();
}, []);

TS-16: Context layering order matters

Outer context = fewer dependencies. Never make inner context depend on outer context's consumer.

MEDIUM: Security Rules

TS-17: Validate all external input with Zod

// API route handler
export async function POST(request: Request) {
  const raw = await request.json();
  const parsed = createItemSchema.safeParse(raw);
  if (!parsed.success) {
    return NextResponse.json({ error: parsed.error.format() }, { status: 400 });
  }
  // Use parsed.data (typed and validated)
}

TS-18: Environment variables validated at startup

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  NEXT_PUBLIC_API_URL: z.string().url(),
});

export const env = envSchema.parse(process.env);
// App won't start with missing/invalid env vars

TS-19: No string interpolation in SQL/HTML

// BAD: SQL injection
const users = await db.$queryRaw`SELECT * FROM users WHERE id = ${id}`;
// This IS safe in Prisma (tagged template), but avoid raw queries when possible

// BAD: XSS
element.innerHTML = `${userInput}`;

// GOOD
element.textContent = userInput;

TS-20: Never expose secrets to client

// BAD: NEXT_PUBLIC_ prefix exposes to browser
NEXT_PUBLIC_DATABASE_URL=postgresql://...

// GOOD: server-only env vars have no prefix
DATABASE_URL=postgresql://...
JWT_SECRET=...

HIGH: Modern Type Patterns

TS-21: Exhaustiveness checks on unions

function assertNever(value: never): never {
  throw new Error(`Unhandled case: ${JSON.stringify(value)}`);
}

type Status = "idle" | "loading" | "success" | "error";

function render(s: Status) {
  switch (s) {
    case "idle": return ;
    case "loading": return ;
    case "success": return ;
    case "error": return ;
    default: return assertNever(s); // Compile error if a case is added
  }
}

Add a new union member without updating render() and the build fails.

TS-22: readonly for inputs you do not own

// BAD: caller can mutate, function can mutate
function totalPrice(items: Item[]): number { ... }

// GOOD: input is read-only at the type level
function totalPrice(items: ReadonlyArray): number { ... }

// Component props
type Props = { readonly user: User; readonly onSave: () => void };

Default props and shared state to readonly. Mutate only your own scope.

TS-23: satisfies over as for typed literals

type RouteConfig = Record;

// BAD: widens, lets you typo keys
const routes = { home: { path: "/", auth: false } } as RouteConfig;

// GOOD: validates shape, keeps narrow keys
const routes = {
  home: { path: "/", auth: false },
  settings: { path: "/settings", auth: true },
} satisfies RouteConfig;

routes.home.path; // "/" inferred, not string

satisfies removes most legitimate uses of as.

HIGH: Modern Runtime Patterns

TS-24: using / await using for explicit resource management

// Old: try/finally with manual cleanup, easy to forget
async function readConfig() {
  const handle = await fs.open(path);
  try {
    return await handle.readFile();
  } finally {
    await handle.close();
  }
}

// New: scoped disposal, runs even on throw
async function readConfig() {
  await using handle = await fs.open(path);
  return await handle.readFile();
} // close() runs here automatically

Any object with Symbol.dispose (sync) or Symbol.asyncDispose works. Native in TS 5.2+, runtime support in Node 22+ and modern browsers.

TS-25: NoInfer to anchor inference

// BAD: T is inferred from defaultValue, not the intended source
function pick(values: T[], defaultValue: T): T { ... }
pick(["a", "b"], 1); // T inferred as string | number, no error

// GOOD: defaultValue cannot widen T
function pick(values: T[], defaultValue: NoInfer): T { ... }
pick(["a", "b"], 1); // Error: 1 is not "a" | "b"

Use on parameters that should accept the inferred type but not contribute to it.

tsconfig.json Baseline

Every project must have at minimum:

{
  "compilerOptions": {
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    "moduleResolution": "bundler",
    "paths": { "@/*": ["./src/*"] }
  }
}

For maximum strictness (Chrome extensions, security-critical, Node native TS):

{
  "compilerOptions": {
    "noUncheckedSideEffectImports": true,
    "exactOptionalPropertyTypes": true,
    "erasableSyntaxOnly": true,
    "isolatedDeclarations": true
  }
}

erasableSyntaxOnly (TS 5.8+) bans enums, namespaces, and parameter properties so source runs unmodified under Node's native TypeScript stripping. isolatedDeclarations (TS 5.5+) requires explicit return types on exported APIs, enabling parallel build and faster type-checking.

Version Targets

  • TypeScript 6.0 (Mar 2026) is the current stable. Lowest target is now ES2015, target: es5 deprecated. New code should drop ES5 polyfills.
  • TypeScript 7.0 beta (Apr 2026) ships a native compiler ported to Go, ~10x faster than 6.0 with the same type system. Migrate when stable, no source changes required.
  • The --upsert Map/WeakMap methods (getOrInsert, getOrInsertComputed) are usable with the new lib targets.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [0xMassi](https://github.com/0xMassi)
- **Source:** [0xMassi/claude-skills](https://github.com/0xMassi/claude-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.