agent/skills/knowledge/typescript/SKILL.md
Type-safe JavaScript with bun and vitest. Use when setting up tsconfig.json, defining interfaces, writing branded types, discriminated unions, or type guards.
npx skillsauth add knoopx/pi typescriptInstall 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.
Type-safe JavaScript development with bun and vitest in this project.
bun init --typescript # Initialize project
bunx tsc --noEmit # Type check
vitest run # Run tests
This project uses bun with ESNext targets. Reference config in templates/tsconfig.json:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"lib": ["ESNext"],
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
},
"include": ["src"],
"exclude": ["node_modules"]
}
Use this when choosing between interface and type:
// Interface — extendable object shape
interface User {
id: number;
name: string;
email: string;
}
// Type — unions and computed types
type Status = "loading" | "success" | "error";
type CreateUser = (data: Partial<User>) => Promise<User>;
// Discriminated union — use type, not interface
type Result<T> = { ok: true; value: T } | { ok: false; error: Error };
Use branded types when a plain string or number could be confused across domains:
type UserId = string & { readonly __brand: "UserId" };
type OrderId = string & { readonly __brand: "OrderId" };
function createUserId(id: string): UserId {
// validate format here
return id as UserId;
}
// Compiler prevents: getUser(orderId) — type mismatch
function getUser(id: UserId): Promise<User> {
/* ... */
}
Model states as discriminated unions so illegal combinations are compile errors:
type AsyncState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };
// Impossible to have data without status: "success"
function render(state: AsyncState<User[]>) {
switch (state.status) {
case "success":
return renderTable(state.data);
case "error":
return renderError(state.error);
// exhaustiveness: TS errors if a case is missing
}
}
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value
);
}
Prefer Result<T> over try/catch for composable error handling:
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
function safeParse(json: string): Result<unknown> {
try {
return { ok: true, value: JSON.parse(json) };
} catch (error) {
return { ok: false, error: error as Error };
}
}
bun init --typescript, copy templates/tsconfig.jsonbunx tsc --noEmit — fix errors before proceedingtmux new -d -s tsc 'tsc --watch' for continuous feedbackvitest run to validate behavior matches typesallowJs: true + checkJs: true when migrating JS files incrementallystrict: true — never weaken strictness per-file with // @ts-ignore; use // @ts-expect-error with a comment explaining whyany — use unknown and narrow with type guards@/* mapped to src/* for importstools
Inform the user what is happening — skip passive lookups
development
Renders markdown to self-contained HTML with a custom dark stylesheet and opens in browser. Use when previewing markdown documents, generating styled HTML from README or report files.
testing
Programmatic hunk selection for Jujutsu — split, commit, or squash specific hunks without interactive prompts. Use when making partial commits or selective squashes.
content-media
Manage version control with Jujutsu (jj) — no staging area, immediate changes, smart rebasing. Use when navigating history, squashing, or pushing to Git remotes.