typescript — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited typescript (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<!-- target: ~2500 tokens (real tiktoken count) | 17 rules with severity classification -->
Purpose: Prevents the TypeScript-specific mistakes LLMs make repeatedly — weak types, unsafe assertions, and patterns that compile but fail at runtime.
Where these rules don't strictly apply: test fixtures, generated types (e.g. from GraphQL codegen, OpenAPI generators, Prisma), declaration files (*.d.ts) for untyped third-party libraries, and migration scripts may legitimately differ. The rules below apply to production application code.
any disables the type checker entirely. unknown forces you to prove the type before use. Exception: third-party libraries without types and explicit dynamic-data boundaries (e.g. JSON parse at the API edge), with a comment explaining why. // Wrong
function parse(data: any) { return data.name; }
// Correct
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 shape');
}Record<string, unknown> for arbitrary objects or define an explicit interface. // Wrong
function merge(a: {}, b: Object): {} { ... }
// Correct
function merge<T extends Record<string, unknown>>(a: T, b: Partial<T>): T { ... }satisfies instead. // Wrong — silences the error, hides the bug
const user = response.data as User;
// Correct — validate first
function isUser(v: unknown): v is User {
return typeof v === 'object' && v !== null && 'id' in v && 'email' in v;
}
const user = isUser(response.data) ? response.data : null; // Avoid
function process(ids: string[]) { ids.push('extra'); }
// Prefer
function process(ids: readonly string[]) { /* ids.push() is a compile error */ }?. returns undefined — always handle that branch. // Wrong
const name = user?.profile.name.toUpperCase(); // crashes if name is undefined
// Correct
const name = user?.profile.name?.toUpperCase() ?? 'Anonymous';UserId where an OrderId is expected — both are string at runtime. type UserId = string & { readonly _brand: 'UserId' };
type OrderId = string & { readonly _brand: 'OrderId' };
function createUserId(raw: string): UserId { return raw as UserId; }
function getUser(id: UserId): User { ... }
// getUser(orderId) → compile error // Avoid — 8 possible combinations, most invalid
type Request = { loading?: boolean; data?: User; error?: Error };
// Prefer — exactly 3 valid states
type Request =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User }
| { status: 'error'; error: Error };as const preserves literals; satisfies validates against an interface without losing them. const config = {
host: 'localhost',
port: 5432,
} satisfies DatabaseConfig;
// config.port is still typed as 5432, not number // Avoid
enum Direction { Up, Down, Left, Right }
// Prefer
const Direction = { Up: 'Up', Down: 'Down', Left: 'Left', Right: 'Right' } as const;
type Direction = typeof Direction[keyof typeof Direction]; // Wrong — index.ts re-exports everything, A imports B through index, B imports A through index
export * from './userService';
export * from './orderService';
// Correct — import directly
import { getUser } from './services/userService';catch (e) gives you unknown in strict mode. Narrow before accessing properties. try {
await fetchUser(id);
} catch (e) {
// Wrong: e.message — e is unknown
// Correct:
const message = e instanceof Error ? e.message : String(e);
logger.error('fetchUser failed', { message, id });
} type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
function parseConfig(raw: string): Result<Config> {
try {
return { ok: true, value: JSON.parse(raw) };
} catch (e) {
return { ok: false, error: e instanceof Error ? e : new Error(String(e)) };
}
} // Wrong — 300ms total if each takes 100ms
const user = await getUser(id);
const orders = await getOrders(id);
const prefs = await getPrefs(id);
// Correct — 100ms total
const [user, orders, prefs] = await Promise.all([
getUser(id),
getOrders(id),
getPrefs(id),
]);Promise.all rejects on the first failure. allSettled collects all results. const results = await Promise.allSettled(ids.map(fetchItem));
const succeeded = results
.filter((r): r is PromiseFulfilledResult<Item> => r.status === 'fulfilled')
.map(r => r.value); // Wrong
const mockUser = { id: '1' } as any;
// Correct
const mockUser: User = { id: createUserId('1'), email: '[email protected]', name: 'Alice' };status variant is a separate code path. One test per branch minimum. it('renders error state', () => {
const state: Request = { status: 'error', error: new Error('timeout') };
render(<RequestView state={state} />);
expect(screen.getByRole('alert')).toHaveTextContent('timeout');
}); import { expectTypeOf } from 'vitest';
expectTypeOf(createUserId('x')).toEqualTypeOf<UserId>();These rules target the gap between "TypeScript that compiles" and "TypeScript that is safe". LLMs default to any, skip discriminated unions, and reach for as assertions because they are the path of least resistance. Each rule here closes a specific escape hatch that lets type errors reach production. The MUST/SHOULD/AVOID classification means safety-critical rules are strict and stylistic rules respect context.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.