install
source · Clone the upstream repo
git clone https://github.com/alinaqi/claude-bootstrap
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/alinaqi/claude-bootstrap "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/typescript" ~/.claude/skills/alinaqi-claude-bootstrap-typescript && rm -rf "$T"
manifest:
skills/typescript/SKILL.mdsource content
TypeScript Skill
Strict Mode (Non-Negotiable)
// 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)
// package.json scripts { "scripts": { "lint": "eslint src/ --ext .ts,.tsx", "typecheck": "tsc --noEmit", "test": "jest", "test:coverage": "jest --coverage", "format": "prettier --write 'src/**/*.ts'" } }
// 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
// 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
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:
npm install -D husky lint-staged npx husky init
// package.json { "lint-staged": { "*.{ts,tsx}": [ "eslint --fix", "prettier --write" ] } }
# .husky/pre-commit npx lint-staged npx tsc --noEmit npm run test -- --onlyChanged --passWithNoTests
This runs on every commit:
- ESLint + Prettier on staged files
- Type check entire project
- Tests for changed files only
Type Patterns
Discriminated Unions for Results
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
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
const STATUSES = ['pending', 'active', 'closed'] as const; type Status = typeof STATUSES[number]; // 'pending' | 'active' | 'closed'
Zod for Runtime Validation
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
- ❌
type - useany
and narrowunknown - ❌ Type assertions (
) - use type guardsas - ❌ Non-null assertions (
) - handle null explicitly! - ❌
without explanation@ts-ignore - ❌ Enums - use const objects or union types
- ❌ Classes for data - use interfaces/types
- ❌ Default exports - use named exports