factory-ai/ban-type-assertions
Ban `as` type assertions in a package via the `@typescript-eslint/consistent-type-assertions` lint rule, replacing them with compiler-verified type-safe alternatives. Use when enabling the assertion ban in a new package or fixing violations in an existing one.
npx skills add https://github.com/Factory-AI/factory-plugins --skill ban-type-assertions
Enable @typescript-eslint/consistent-type-assertions with assertionStyle: 'never' in a package and replace all as X casts with patterns the compiler can verify.
> Pick the strictly correct path, not the simpler one.
Every as assertion is a spot where the developer told the compiler "trust me." The goal is to make the compiler *verify* instead. If you replace as Foo with a type guard that is equally unverified, you have not improved anything -- you have just moved the assertion.
@typescript-eslint/consistent-type-assertions{ assertionStyle: 'never' }packages/<name>/.eslintrc.jsAdd to the package's .eslintrc.js:
rules: {
'@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'never' }],
}
cd packages/<name> && npm run lint 2>&1 | grep "consistent-type-assertions"
Group violations by file and pattern before fixing.
Before writing any replacement code:
Schema alongside the type name in @factory/common and across the repo.@factory/common if found.Use for any data entering the system from JSON, disk, network, IPC, etc. This gives runtime validation, not just a type annotation.
// BAD
const data = JSON.parse(raw) as MyType;
// GOOD
const data = MySchema.parse(JSON.parse(raw));
Use safeParse when you need to handle errors gracefully (e.g., returning an error response with context like a request id):
// BAD: throws before you can extract the request id
const request = RequestSchema.parse(JSON.parse(raw));
// GOOD: safeParse lets you return a proper error
const parsed = RequestSchema.safeParse(JSON.parse(raw));
if (!parsed.success) {
return errorResponse(rawObj?.id ?? null, INVALID_PARAMS, parsed.error.message);
}
const request = parsed.data;
Use switch, in, instanceof, or discriminated unions:
// BAD
(error as NodeJS.ErrnoException).code
// GOOD
if (error instanceof Error && 'code' in error) {
const code = error.code;
}
// BAD
if (METHODS.has(method as Method)) { ... }
// GOOD: switch narrows exhaustively
switch (method) {
case 'foo':
case 'bar':
return handle(method); // narrowed
}
Only for genuinely unavoidable cases (library type gaps, generic parameters that can't be inferred). Always explain *why*:
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- ws library types require generic parameter
ws.on('message', handler);
// NOT an improvement -- checks shape but not content
function isDaemonRequest(x: unknown): x is DaemonRequest {
return typeof x === 'object' && x !== null && 'method' in x;
}
A zod schema validates values. A type guard like this is an unverified assertion with extra steps. Only use type guards when the narrowing logic is truly sufficient.
When a schema exists (e.g., SessionSettingsSchema), use it strictly rather than z.record(z.unknown()). This ensures forward compatibility -- if fields are removed in a migration, stale data gets cleaned on read.
// BAD: accepts anything
const settings = z.record(z.unknown()).parse(raw);
// GOOD: validates against the real shape
const settings = SessionSettingsSchema.parse(raw);
@factory/commonIf you find duplicate interfaces, types, or schemas across packages, consolidate them:
@factory/common/<domain>/<subdomain>/schema.tsenums.ts (required by factory/enum-file-organization)@factory/common/session/summary), not the barrel index.tsnpm run knip at repo root to catch unused barrel re-exportsOnce you replace as X with .parse(), test mocks that relied on the assertion will fail validation. Fix the mocks -- do not disable the rule in tests.
Create helper functions to centralize valid test fixtures:
function mockSessionSummary(
overrides?: Partial<SessionSummaryEvent>,
): SessionSummaryEvent {
return {
type: 'session_start',
id: 'test-id',
title: 'Test Session',
owner: 'test-owner',
...overrides,
};
}
Make sure parsing happens where failures produce proper error responses, not unhandled exceptions:
// BAD: parse outside try/catch -- if it throws, you lose context
const request = RequestSchema.parse(data);
try { handle(request); } catch { ... }
// GOOD: safeParse before try, handle error with context
const parsed = RequestSchema.safeParse(data);
if (!parsed.success) {
return errorResponse(rawData?.id ?? null, INVALID_PARAMS, parsed.error.message);
}
try { handle(parsed.data); } catch { ... }
Run for all affected packages (a change in @factory/common can break downstream lint):
# Lint (all affected packages)
cd packages/<name> && npm run lint
# Typecheck
npm run typecheck
# Tests
npm run test
# Unused exports (repo root)
npm run knip
factory/enum-file-organization requires TypeScript enums to live in files named enums.tsno-barrel-files prevents re-exporting types from barrel files -- consumers must import from the subpath directlypackage.json exports entry for the new subpath if one doesn't exist.eslintrc.js may be needed if test files use assertion syntax in mock setup -- but prefer fixing mocks over disabling the ruleTake factory-ai/ban-type-assertions from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.