plugins/typescript-development/skills/typescript-write/SKILL.md
Write TypeScript and JavaScript code following modern best practices and coding standards. TRIGGER WHEN: writing or reviewing TypeScript/JavaScript code, including types, generics, async patterns, module boundaries, and style conventions. DO NOT TRIGGER WHEN: the task is framework-specific React performance (use react-development), Node.js build/packaging, or enterprise-grade deep-dive (use mastering-typescript skill).
npx skillsauth add acaprino/anvil-toolset typescript-writeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
camelCase for variables, functions, parametersPascalCase for types, interfaces, classes, enums, React componentsUPPER_SNAKE_CASE for constants and enum membersI only if project convention requires it - otherwise plain PascalCaseis, has, should, can prefixes (isLoading, hasPermission)handleClick, onSubmit patternindex.ts) for public module APIs only - avoid deep barrel re-exportskebab-case.ts for utilities, PascalCase.tsx for React componentsnode:fs, node:path)react, lodash)@/utils, @/components)./helpers, ../types)import type { Foo })strict: true in tsconfig.json - never disable individual strict checks// @ts-ignore or // @ts-expect-error without an explanatory commentunknown over any - narrow with type guardsany - use unknown and narrow, or define a proper typeinterface for object shapes that may be extendedtype for unions, intersections, mapped types, and utility typesreadonly for properties that should not be mutatedas const for literal type inference on objects and arraystype Result<T> =
| { success: true; data: T }
| { success: false; error: Error };
function handle<T>(result: Result<T>) {
if (result.success) {
// result.data is T here
return result.data;
}
// result.error is Error here
throw result.error;
}
// User-defined type guard
function isString(value: unknown): value is string {
return typeof value === "string";
}
// Assertion function
function assertDefined<T>(value: T | undefined, name: string): asserts value is T {
if (value === undefined) {
throw new Error(`Expected ${name} to be defined`);
}
}
// Constrain generics to what you actually need
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// Use defaults for common cases
type ApiResponse<T = unknown> = {
data: T;
status: number;
timestamp: string;
};
Partial<T> - all properties optional (use for update/patch operations)Required<T> - all properties requiredPick<T, K> - select subset of propertiesOmit<T, K> - exclude propertiesRecord<K, V> - typed key-value mapExtract<T, U> / Exclude<T, U> - filter union memberstype Status = "active" | "inactive"const enum only if you need numeric values and tree-shakingenum when you need runtime reverse mapping or iteration// Function components - type props inline or with interface
interface ButtonProps {
label: string;
variant?: "primary" | "secondary";
onClick: () => void;
children?: React.ReactNode;
}
function Button({ label, variant = "primary", onClick, children }: ButtonProps) {
return <button className={variant} onClick={onClick}>{children ?? label}</button>;
}
use prefixuseCallback for functions passed as props to memoized childrenuseMemo for expensive computations - not for every variableuseReducer for complex state with multiple sub-values or transitions// Type event handlers properly
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setValue(e.target.value);
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
// ...
}
*.test.ts or *.spec.ts alongside the source file__tests__/ directory mirroring the source structuretest-utils.ts or testing/ directorydescribe("calculateTotal", () => {
it("returns 0 for empty cart", () => {
expect(calculateTotal([])).toBe(0);
});
it("sums item prices with quantities", () => {
const items = [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 1 },
];
expect(calculateTotal(items)).toBe(25);
});
it("throws for negative quantities", () => {
expect(() => calculateTotal([{ price: 10, quantity: -1 }])).toThrow();
});
});
toBe for primitives, toEqual for objects/arraystoThrow for error cases - wrap in arrow functiontoHaveBeenCalledWith for spy/mock assertionstoBeTruthy/toBeFalsyas any in testsvi.fn() (Vitest) or jest.fn() for function mocksvi.spyOn / jest.spyOn to mock methods while preserving type safetybeforeEach or use afterEach(() => vi.restoreAllMocks())any Instead of Proper Types// BAD
function parse(data: any) { return data.name; }
// GOOD
function parse(data: unknown): string {
if (typeof data === "object" && data !== null && "name" in data) {
return String((data as { name: unknown }).name);
}
throw new Error("Invalid data");
}
// BAD
const name = user!.name!;
// GOOD
if (!user?.name) throw new Error("User name required");
const name = user.name;
// BAD - importing everything through deep barrel
import { Button } from "@/components"; // pulls entire component tree
// GOOD - direct import
import { Button } from "@/components/Button";
// BAD - return type inferred as complex union
function getData(id: string) {
if (!id) return null;
return fetch(`/api/${id}`).then(r => r.json());
}
// GOOD - explicit return type
async function getData(id: string): Promise<ApiResponse | null> {
if (!id) return null;
const r = await fetch(`/api/${id}`);
return r.json() as Promise<ApiResponse>;
}
// BAD
function addItem(items: Item[], item: Item) {
items.push(item); // mutates input
return items;
}
// GOOD
function addItem(items: readonly Item[], item: Item): Item[] {
return [...items, item];
}
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
async function fetchUser(id: string): Promise<Result<User>> {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) return { ok: false, error: new Error(`HTTP ${res.status}`) };
const user = await res.json();
return { ok: true, value: user };
} catch (e) {
return { ok: false, error: e instanceof Error ? e : new Error(String(e)) };
}
}
try/catch around await calls that can fail.catch() and await on the same promise chaincomponentDidCatchclass NotFoundError extends Error {
readonly code = "NOT_FOUND" as const;
constructor(resource: string, id: string) {
super(`${resource} ${id} not found`);
this.name = "NotFoundError";
}
}
class ValidationError extends Error {
readonly code = "VALIDATION" as const;
constructor(public readonly fields: Record<string, string>) {
super("Validation failed");
this.name = "ValidationError";
}
}
development
Quality gates for multi-reviewer code review pipelines: adversarial verification panel, completeness critic, reviewer pipeline conventions, and the context sharing pattern for parallel reviewers. TRIGGER WHEN: running /senior-review:team-review quality gates; running /senior-review:code-review Steps 4b/4c (adversarial verification and completeness check); consolidating or deduplicating findings from multiple parallel reviewers. DO NOT TRIGGER WHEN: single-reviewer style review without a consolidation phase, or generic team coordination (the upstream agent-teams skills cover that).
development
Knowledge base for pure-architecture decisions on when to unify duplicated logic into a shared abstraction versus leave it duplicated. Covers the canonical theory (Rule of Three, DRY/WET/AHA, Wrong Abstraction, Locality of Behaviour, Bounded Contexts, Tidy First options framing, CUPID vs SOLID), 12 essential-duplication patterns that justify unification, 12 wrong-abstraction patterns that justify inlining or decomposition, an operational decision frame, and a verified reading list. TRIGGER WHEN: the user is making an architectural decision about whether to centralize, extract, or remove a layer; reviewing an abstraction for premature generality; auditing scattered cross-cutting concerns; spawned by the abstraction-architect agent during /abstraction-architect:audit or as the Abstraction dimension of /senior-review:team-review or /senior-review:code-review; the user asks "should I extract this into a service" / "is this DRY enough" / "is this wrong abstraction". DO NOT TRIGGER WHEN: the task is code formatting and readability cleanup (use clean-code:clean-code), Python-specific refactoring with metrics (use python-development:python-refactor), generic dead-code removal (use senior-review:cleanup-dead-code), security review (use senior-review:security-auditor), or pure pattern-consistency review without an architecture lens (use senior-review:code-auditor).
development
Unified web frontend knowledge base covering CSS architecture, UX psychology, UI components, distinctive aesthetics, and interface design generation. TRIGGER WHEN: working on web styling, design systems, component decisions, responsive strategy, distinctive frontend aesthetics, or exploring multiple interface designs. DO NOT TRIGGER WHEN: the task is purely backend or unrelated to web frontend.
development
Stripe payments knowledge base - API patterns, checkout optimization, subscription lifecycle, pricing strategies, webhook reliability, Firebase integration, cost analysis, and revenue modeling. Loaded by stripe-integrator and revenue-optimizer agents; also consumable directly when the user asks for Stripe-specific patterns without needing an agent. TRIGGER WHEN: working with Stripe API (Payment Intents, Customers, Subscriptions, Checkout Sessions, Connect, webhooks, tax, usage-based billing), pricing strategy, or revenue modeling. DO NOT TRIGGER WHEN: payment work is non-Stripe (PayPal, Square, crypto) or the task is generic e-commerce unrelated to payments.