TY

typescript-safety

Secures TypeScript message boundaries using Zod validation and strict type safety rules.

Install

mkdir -p .claude/skills/typescript-safety && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/18445" && unzip -o skill.zip -d .claude/skills/typescript-safety && rm skill.zip

Installs to .claude/skills/typescript-safety

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Use when writing TypeScript at message boundaries, runtime inputs, shared contracts, or any code that crosses the content-script/SW/backend trust boundary — to enforce Zod validation, discriminated unions, no-any rules, and sender verification.
244 chars✓ has a “when” trigger
Advanced

Key capabilities

  • Validate messages with Zod schemas at SW port boundaries
  • Verify message sender before processing port messages
  • Enforce no `any` in shared contracts
  • Use `unknown` and narrowing for I/O boundaries
  • Implement discriminated unions for message verb dispatch
  • Enable strict mode in `tsconfig.json`

How it works

The skill enforces type safety by validating all inbound messages with Zod schemas, verifying senders, and applying strict TypeScript rules at trust boundaries.

Inputs & outputs

You give it
Runtime inputs from external sources or messages at SW port boundary
You get back
Type-safe and validated data, or a warning for invalid message shapes

When to use typescript-safety

  • Validate runtime inputs from external sources
  • Secure message handlers in background scripts
  • Define type-safe contract interfaces
  • Prevent runtime crashes from malformed JSON

About this skill

TypeScript Safety

When to Use

Use this skill when:

  • writing or reviewing message handlers at the SW port boundary
  • defining or consuming types in shared/contracts/
  • parsing runtime inputs from external sources (backend SSE, storage, page DOM)
  • any code uses as, any, or untyped JSON.parse
  • TypeScript errors are flagged by the post-edit verification hook

This skill is triggered by the index.ts error hotspot pattern. The 14+ recurring errors in pending-improvements.md trace largely to unguarded message shapes and implicit any in I/O paths.

When Not to Use

Do not use this skill for:

  • pure CSS or DOM geometry work with no runtime parsing
  • documentation-only changes

Files and Surfaces

Primary files:

  • extension/src/content/index.ts
  • extension/src/background/index.ts
  • shared/contracts/domain.ts
  • shared/contracts/sse.ts
  • backend/src/lib/schemas.ts

Core Rules

Rule 1: Zod Schemas at Every Message Boundary

All messages received at the SW port handler must be validated with a Zod schema before dispatch. Use safeParse — never parse — inside message handlers.

import { z } from 'zod';

const InboundMessageSchema = z.discriminatedUnion('verb', [
  z.object({ verb: z.literal('SEGMENT'), payload: SegmentPayloadSchema }),
  z.object({ verb: z.literal('ENHANCE'), payload: EnhancePayloadSchema }),
  z.object({ verb: z.literal('BIND'),    payload: BindPayloadSchema }),
  z.object({ verb: z.literal('CANCEL'),  payload: z.object({ tabId: z.number() }) }),
]);

port.onMessage.addListener((raw) => {
  const result = InboundMessageSchema.safeParse(raw);
  if (!result.success) {
    console.warn('[SW] invalid message shape', result.error.flatten());
    return; // reject without throwing
  }
  dispatch(result.data);
});

Rule 2: Sender Verification Before Processing

Verify the message sender before acting on any port message. Reject messages from unknown origins silently.

chrome.runtime.onConnect.addListener((port) => {
  // Only accept connections from this extension's own content scripts
  if (port.sender?.id !== chrome.runtime.id) {
    port.disconnect();
    return;
  }
  // proceed with message handling
});

Rule 3: No any in Shared Contracts

All types in shared/contracts/ must be fully typed. Ban any with a lint rule or a code comment policy.

// WRONG
export type Section = { goal_type: any; text: any };

// CORRECT
export type GoalType = 'action' | 'tech_stack' | 'constraint' | 'output_format' | 'context' | 'edge_case';
export type Section = { goal_type: GoalType; text: string };

Rule 4: Use unknown + Narrowing at All I/O Boundaries

When receiving data from JSON.parse, chrome.storage, or SSE events, type the input as unknown and narrow explicitly.

// CORRECT
chrome.storage.sync.get('mode', (data: unknown) => {
  const result = ModeSchema.safeParse((data as Record<string, unknown>)?.mode);
  const mode = result.success ? result.data : 'balanced';
});

// WRONG
chrome.storage.sync.get('mode', (data: any) => {
  const mode = data.mode; // no validation
});

Rule 5: Discriminated Unions for Message Verb Dispatch

Use TypeScript discriminated unions so the compiler enforces exhaustive handling of all verb types.

type InboundMessage =
  | { verb: 'SEGMENT'; payload: SegmentPayload }
  | { verb: 'ENHANCE'; payload: EnhancePayload }
  | { verb: 'BIND';    payload: BindPayload }
  | { verb: 'CANCEL';  payload: { tabId: number } };

function dispatch(msg: InboundMessage) {
  switch (msg.verb) {
    case 'SEGMENT': return handleSegment(msg.payload);
    case 'ENHANCE': return handleEnhance(msg.payload);
    case 'BIND':    return handleBind(msg.payload);
    case 'CANCEL':  return handleCancel(msg.payload);
    // TypeScript will error here if a new verb is added without a handler
  }
}

Rule 6: tsconfig.json Must Have "strict": true

The extension and backend packages must both enable strict mode. This catches implicit any, null dereferences, and unchecked optional access.

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true
  }
}

Rule 7: Type SSE Events Explicitly

SSE MessageEvent.data is typed as string — parse it explicitly before use.

evtSource.addEventListener('message', (e: MessageEvent<string>) => {
  if (e.data === '[DONE]') return finalize();
  const result = TokenChunkSchema.safeParse(JSON.parse(e.data));
  if (!result.success) return; // malformed token — skip silently
  appendGhostText(result.data.token);
});

Rule 8: Never Cast with as to Skip Validation

Use as only for DOM type widening (as HTMLTextAreaElement) or when TypeScript inference is provably wrong. Never use as to suppress a type error at an I/O boundary.

// ACCEPTABLE — DOM narrowing
const ta = document.getElementById('foo') as HTMLTextAreaElement;

// WRONG — bypasses runtime safety
const msg = rawMessage as BindPayload; // no validation

Deliverables

  • Zod schemas defined for all inbound message shapes
  • safeParse used in all message handlers — no thrown validation errors
  • Sender ID verified before processing any port connection
  • No any in shared/contracts/ files
  • Discriminated union on verb field for the message router
  • "strict": true confirmed in tsconfig.json for extension and backend packages
  • All SSE event data parsed through Zod before use

When not to use it

  • For pure CSS or DOM geometry work with no runtime parsing

Limitations

  • The skill does not apply to pure CSS or DOM geometry work
  • The skill does not apply to documentation-only changes
  • The skill bans `any` in shared contracts

How it compares

This approach systematically applies type safety and validation at critical system boundaries, preventing common runtime errors that manual coding might miss.

Compared to similar skills

typescript-safety side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
typescript-safety (this skill)01moNo flagsAdvanced
tech-debt-analyzer58moReviewIntermediate
codex-code-review17moReviewIntermediate
tech-debt11moReviewBeginner

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

Search skills

Search the agent skills registry