typescript · alinaqi — Claude Code skill: strict mode, eslint, jest
Tells the agent how to write and check TypeScript: strict tsconfig, ESLint config, Jest tests and a layered project layout. Kicks in on .ts and .tsx files.
npx skills add alinaqi/maggy --skill typescript -a claude-codeWhat typescript does
The skill activates on its own when the agent touches files matching **/*.ts, **/*.tsx or tsconfig*.json. Inside is a ready tsconfig.json with strict, noImplicitAny, strictNullChecks, noUnusedLocals, noUnusedParameters, noImplicitReturns and esModuleInterop. Alongside it sits an eslint.config.js built on typescript-eslint with strict rules: no any, explicit return types, a 20-line cap per function, max depth 2 and at most 3 parameters.
Project layout is spelled out too: src/core for pure logic, src/infra for side effects, tests/unit and tests/integration. A Jest example shows the Arrange/Act/Assert split. There is a GitHub Actions workflow running lint, typecheck and test:coverage, with the coverage threshold set to 80% for branches, functions, lines and statements. Commits go through Husky and lint-staged: ESLint with --fix, Prettier, then tsc --noEmit and tests limited to changed files.
The rules are strict. any, as assertions, the ! operator, unexplained @ts-ignore and enums are all listed as anti-patterns. If your codebase relies on enums on purpose, or any is scattered through legacy code, the agent will push back against what is already there. Worth checking before you install the whole thing.
What it helps with
- turn on strict TypeScript in a fresh project
- split code into core, infra and utils layers
- set up ESLint with type-aware rules
- add Jest tests with a coverage gate
- run checks on every commit via Husky
Example prompts for the agent
- Set up tsconfig.json following the typescript skill rules
- Write a Jest test for calculateTotal using Arrange/Act/Assert
- Check this file for TypeScript anti-patterns and replace any
How to install typescript
1Skills CLI (Vercel)
npx skills add alinaqi/maggy --skill typescript -a claude-code2Manually: put the files in place
~/.claude/skills/typescript/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/typescript/
1Skills CLI (Vercel)
npx skills add alinaqi/maggy --skill typescript -a codex2Manually: put the files in place
.agents/skills/typescript/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add alinaqi/maggy --skill typescript -a cursor2Manually: put the files in place
.cursor/skills/typescript/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install alinaqi/maggy typescript2Manually: put the files in place
.github/skills/typescript/SKILL.md1Skills CLI (Vercel)
npx skills add alinaqi/maggy --skill typescript -a gemini-cli2Platform CLI
gemini skills install https://github.com/alinaqi/maggy --path skills/typescriptManually: put the files in place
.devin/skills/typescript/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add alinaqi/maggy --skill typescript -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about typescript
How do I install the typescript skill in Claude?
Run npx skills add alinaqi/maggy --skill typescript -a claude-code. The skill lands at ~/.claude/skills/typescript/SKILL.md.
What does the typescript skill do?
It gives the agent a working set of TypeScript conventions: strict tsconfig, an ESLint config, Jest tests, a directory layout and pre-commit hooks.
When does the skill trigger?
It is bound to **/*.ts, **/*.tsx and tsconfig*.json, so it fires when the agent works with those files. There is no manual invocation.
Source text
SKILL.md · MIT
---
name: typescript
description: TypeScript strict mode with eslint and jest
when-to-use: When working on TypeScript files
user-invocable: false
paths: ["**/*.ts", "**/*.tsx", "tsconfig*.json"]
effort: medium
---
# TypeScript Skill
---
## Strict Mode (Non-Negotiable)
```json
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
```
---
## Project Structure
```
project/
├── src/
│ ├── core/ # Pure business logic
│ │ ├── types.ts # Domain types/interfaces
│ │ ├── services/ # Pure functions
│ │ └── index.ts # Public API
│ ├── infra/ # Side effects
│ │ ├── api/ # HTTP handlers
│ │ ├── db/ # Database operations
│ │ └── external/ # Third-party integrations
│ └── utils/ # Shared utilities
├── tests/
│ ├── unit/
│ └── integration/
├── package.json
├── tsconfig.json
└── CLAUDE.md
```
---
## Tooling (Required)
```json
// package.json scripts
{
"scripts": {
"lint": "eslint src/ --ext .ts,.tsx",
"typecheck": "tsc --noEmit",
"test": "jest",
"test:coverage": "jest --coverage",
"format": "prettier --write 'src/**/*.ts'"
}
}
```
```javascript
// eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
{
rules: {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': 'error',
'max-lines-per-function': ['error', 20],
'max-depth': ['error', 2],
'max-params': ['error', 3],
}
}
);
```
---
## Testing with Jest
```typescript
// tests/unit/services/user.test.ts
import { calculateTotal } from '../../../src/core/services/pricing';
describe('calculateTotal', () => {
it('returns sum of item prices', () => {
// Arrange
const items = [{ price: 10 }, { price: 20 }];
// Act
const result = calculateTotal(items);
// Assert
expect(result).toBe(30);
});
it('returns zero for empty array', () => {
expect(calculateTotal([])).toBe(0);
});
it('throws on invalid item', () => {
expect(() => calculateTotal([{ invalid: 'item' }])).toThrow();
});
});
```
---
## GitHub Actions
```yaml
name: TypeScript Quality Gate
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type Check
run: npm run typecheck
- name: Test with Coverage
run: npm run test:coverage
- name: Coverage Threshold (80%)
run: npm run test:coverage -- --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}'
```
---
## Pre-Commit Hooks
Using Husky + lint-staged:
```bash
npm install -D husky lint-staged
npx husky init
```
```json
// package.json
{
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write"
]
}
}
```
```bash
# .husky/pre-commit
npx lint-staged
npx tsc --noEmit
npm run test -- --onlyChanged --passWithNoTests
```
This runs on every commit:
1. ESLint + Prettier on staged files
2. Type check entire project
3. Tests for changed files only
---
## Type Patterns
### Discriminated Unions for Results
```typescript
type Result<T> =
| { ok: true; value: T }
| { ok: false; error: string };
function parseUser(data: unknown): Result<User> {
// Type-safe error handling without exceptions
}
```
### Branded Types for IDs
```typescript
type UserId = string & { readonly brand: unique symbol };
type OrderId = string & { readonly brand: unique symbol };
// Can't accidentally pass UserId where OrderId expected
function getOrder(orderId: OrderId): Order { ... }
```
### Const Assertions for Literals
```typescript
const STATUSES = ['pending', 'active', 'closed'] as const;
type Status = typeof STATUSES[number]; // 'pending' | 'active' | 'closed'
```
### Zod for Runtime Validation
```typescript
import { z } from 'zod';
const UserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
});
type User = z.infer<typeof UserSchema>;
```
---
## TypeScript Anti-Patterns
- ❌ `any` type - use `unknown` and narrow
- ❌ Type assertions (`as`) - use type guards
- ❌ Non-null assertions (`!`) - handle null explicitly
- ❌ `@ts-ignore` without explanation
- ❌ Enums - use const objects or union types
- ❌ Classes for data - use interfaces/types
- ❌ Default exports - use named exports
© alinaqi · MIT · alinaqi/maggy
Similar skills
All skills for Programming languages →typescript by other authors comes first.