plugins/language-pro/skills/typescript-pro/SKILL.md
Strict TypeScript with zero-any tolerance, no-unsafe-* lints, floating-promise prevention, and disciplined type-system usage. Use when implementing, debugging, refactoring, or reviewing TypeScript code; resolving type errors; configuring tsconfig/ESLint/Prettier; setting up React/Next/Express patterns; eliminating any/unknown drift; or evaluating advanced generics, conditional types, and inference. Applies to any TypeScript work unless a more specific role overrides.
npx skillsauth add rbergman/dark-matter-marketplace typescript-proInstall 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.
Senior-level TypeScript expertise for production projects. Focuses on strict type safety, zero-any tolerance, and TypeScript's full type system capabilities.
tsconfig.json and eslint.config.js for project conventionsRequired:
any - use unknown and narrowas any, as unknown as T)Foundational Principles:
Pin Node version with mise: mise use node@22 (creates .mise.toml — commit it). Team members run mise install. See mise skill for setup.
# Initialize
npm init -y
npm install -D typescript typescript-eslint @eslint-community/eslint-plugin-eslint-comments eslint-plugin-sonarjs prettier lint-staged vitest
# Add scripts to package.json:
npm pkg set scripts.typecheck="tsc --noEmit"
npm pkg set scripts.lint="eslint src/"
npm pkg set scripts.test="vitest run"
npm pkg set scripts.check="npm run typecheck && npm run lint && npm run test"
# Configure lint-staged (formats only staged files on commit)
npm pkg set lint-staged --json '{"*.{ts,tsx}": ["prettier --write"], "*.{json,yml,yaml}": ["prettier --write"]}'
# Create .prettierignore (prevent formatting machine-generated and non-TS files)
cat > .prettierignore << 'EOF'
# ============================================================================
# DO NOT ADD SOURCE FILES HERE TO WORK AROUND LINE LENGTH LIMITS.
#
# If prettier expansion pushes a file past max-lines (400) or
# max-lines-per-function (60), the file needs to be DECOMPOSED — extract
# functions, split into modules, rearchitect. That is the engineering fix.
#
# Adding source files here suppresses formatting without fixing the real
# problem. The line limits are design signals, not obstacles to route around.
# ============================================================================
# Machine-generated / non-source (safe to exclude)
coverage/
dist/
node_modules/
.worktrees/
.timbers/
.beads/
EOF
# Verify
npm run check
Quality gates run via a git pre-commit hook. With beads 1.0+, hooks live in .beads/hooks/ (committed to git, managed by bd hooks install --beads). Beads, timbers, and your quality gates all coexist in the same hook file via section markers — content outside markers is preserved across reinstalls.
Setup:
bd init (creates .beads/hooks/ and sets core.hooksPath = .beads/hooks)timbers hooks install (detects core.hooksPath, appends into existing files alongside beads)bd hooks install --force and timbers hooks install rerunsPre-commit hook structure (.beads/hooks/pre-commit):
#!/usr/bin/env sh
# --- BEGIN BEADS INTEGRATION v1.0.x --- (managed — do not edit)
# ... bd hooks run pre-commit shim ...
# --- END BEADS INTEGRATION v1.0.x ---
# Quality gates (preserved across reinstalls — outside markers)
if [ -f .git/MERGE_HEAD ]; then
echo "Merge commit — skipping lint-staged"
else
npx lint-staged
fi
if command -v just >/dev/null 2>&1 && [ -f justfile ]; then
just check
else
npm run check
fi
# --- timbers section (managed by timbers hooks install)
# ... timbers hook run pre-commit shim ...
# --- end timbers section ---
Why this order: Beads runs first (fast: bd's internal hook handles auto-export+stage of .beads/issues.jsonl). Quality gates run next (slowest, may fail — but bd export already happened, so beads state is captured even if gates fail and the commit aborts). Timbers runs last (post-gate, post-export).
Auto-export defaults (beads 1.0+):
export.auto = true — every bd mutation writes .beads/issues.jsonl (60s throttled)export.git-add = true — auto-stages itJustfile recipe:
hooks:
@bd hooks install --force --beads >/dev/null && echo " ✅ beads hooks installed"
@if command -v timbers >/dev/null 2>&1; then \
timbers hooks install >/dev/null && echo " ✅ timbers hooks installed"; \
fi
@current=$(git config --get core.hooksPath 2>/dev/null || true); \
if [ "$current" != ".beads/hooks" ]; then \
git config core.hooksPath .beads/hooks; \
echo " ✅ core.hooksPath fixed to .beads/hooks (was $current)"; \
fi
Note on core.hooksPath: bd hooks install --force may set this to an absolute path. Force it relative — worktrees share repo config, and an absolute path won't resolve from a worktree's working dir.
New dev/agent onboarding: git clone <repo> && just setup (which includes just hooks).
If a project currently uses husky, migrate:
npm uninstall husky && rm -rf .husky && npm pkg delete scripts.prepare
bd hooks install --force --beads
git config core.hooksPath .beads/hooks
# Move any custom hook content into .beads/hooks/<hook> outside the BEADS markers
In monorepos (multiple packages, possibly mixed languages), adjust the setup:
lint-staged: scoped to TS packages only. Don't format Go/Rust code with Prettier — they have their own formatters (goimports, rustfmt).
# Root package.json (npm workspaces / turborepo):
npm pkg set lint-staged --json '{"packages/web/**/*.{ts,tsx}": ["prettier --write"], "*.{json,yml,yaml}": ["prettier --write"]}'
# Or independent packages (no workspaces): install lint-staged per TS package
Pre-commit: lint-staged only, no npm run check. Full quality gates across all packages are too slow for pre-commit. Run lint-staged in the hook, run full gates via just check or CI.
# .beads/hooks/pre-commit (between BEADS markers and timbers section):
npx lint-staged
(beads 1.0+ auto-exports + auto-stages .beads/issues.jsonl on every mutation; the manual export+stage lines older docs showed are no longer needed.)
For mixed-language monorepos without workspaces, detect which packages have staged files:
if git diff --cached --name-only | grep -q '^packages/web/'; then
(cd packages/web && npx lint-staged)
fi
.prettierrc: root-level. Prettier walks up the directory tree, so a single root config covers all TS packages. Use per-package configs only if packages need different formatting.
Required Config Files: Copy references/gitignore → .gitignore, references/prettierrc.json → .prettierrc, then create tsconfig.json and eslint.config.js per the templates below.
git clone <repo> && cd <repo>
just setup # Runs mise trust/install + npm ci
just check # Verify everything works
Or manually:
mise trust && mise install # Get pinned Node version
npm ci # Get dependencies
Why strict configs? Type errors caught at compile time are 10x cheaper than runtime bugs. Strict linting prevents any from leaking through the codebase.
Invoke the just-pro skill for build system setup. It covers:
references/package-ts.just)Alternative: Use npm scripts directly if just is unavailable.
Auto-Fix First - Always try auto-fix before manual fixes:
npx prettier --write src/ # Format changed files
npx eslint src/ --fix # Fixes style, imports, etc.
npx tsc --noEmit # Type check without emit
Verification:
npm run check # typecheck + lint + test
npm audit --omit=dev --audit-level=high # vulnerability check (production deps only)
Or via just (which combines both):
just check
Pre-commit Hook (git hook with lint-staged):
npm run check runs typecheck + lint + test.beads/hooks/pre-commit alongside beads/timbers hooks (not husky, not .git/hooks/).prettierignore must exclude .timbers/ and .beads/ — without this, lint-staged reformats timbers JSON during commit, but its stash/restore cycle puts the original format back in the working tree, creating perpetual MM diffs with no semantic contentWhen creating a new project, copy references/eslint.config.js from this skill — it's the canonical template. Omitting rules allows any to leak through the codebase.
The template enforces four rule families:
| Family | Purpose | Key rules |
|--------|---------|-----------|
| Type safety | Block any and unsafe-* drift | no-explicit-any, no-unsafe-{argument,assignment,call,member-access,return,type-assertion}, no-non-null-assertion |
| Promises | Catch unhandled async | no-floating-promises, no-misused-promises, require-await, promise-function-async |
| Complexity (extraction signal) | Force decomposition by responsibility, not compression | complexity: 10, sonarjs/cognitive-complexity: 15, max-depth: 4, max-len: 120, max-lines-per-function: 60, max-lines: 400, max-params: 4 |
| Disable governance | Stop ad-hoc disabling of critical rules | @eslint-community/eslint-comments/no-restricted-disable blocks disabling type-safety, promise, and complexity rules without an explicit override |
Tests (**/*.test.ts, **/*.spec.ts) relax any, complexity, and line limits.
These limits exist to improve code architecture, not to be gamed. When a file or function exceeds a limit, the correct response is to decompose by responsibility — not to make the code fit by any means necessary.
Extract, don't compress:
order-service.ts → order-validation.ts, order-transforms.ts)When extraction is costly (many locals to pass), use a context/options object. If splitting would duplicate state, the code may need a different decomposition axis (by entity rather than by phase).
Prohibited responses to limit violations:
max-len at 120 catches this — the line limit and file limit work together).prettierignore so prettier won't expand them backAny of these trades one problem (length) for a worse one (readability). The goal is clean architecture, not metric compliance. Prettier enforces consistent formatting, so compressed code will be expanded back to its readable form — and max-len prevents the line-combining workaround entirely. Extraction is the only sustainable fix.
| Limit | Value | Purpose |
|-------|-------|---------|
| max-len | 120 chars | Prevent line-combining to dodge file/function limits |
| max-lines | 400 code | Prevent god modules (comments excluded) |
| max-lines-per-function | 60 | Single responsibility |
| complexity | 10 | Cyclomatic complexity (branching paths) |
| sonarjs/cognitive-complexity | 15 | Cognitive complexity (perceived difficulty) |
| max-depth | 4 | Avoid arrow code |
| max-params | 4 | Use options objects |
Critical rules cannot be disabled via eslint-disable comments - the config blocks it.
| Pattern | Use |
|---------|-----|
| unknown over any | Safe default for unknown types |
| Type guards | Runtime narrowing with type safety |
| Discriminated unions | State machines, tagged unions |
| Branded types | Domain modeling (UserId vs string) |
| satisfies operator | Validate without widening |
| as const | Literal types from values |
| Pattern | Use |
|---------|-----|
| Result<T, E> type | Explicit success/failure |
| never exhaustive check | Catch unhandled cases |
| Custom error classes | Typed error discrimination |
| Zod validation | Runtime + compile-time safety |
inferT[K])project/
├── src/
│ ├── index.ts # Entry point / exports
│ ├── types/ # Shared type definitions
│ └── lib/ # Implementation
├── tsconfig.json
├── eslint.config.js
├── package.json
└── justfile
Rules: One module = one purpose. Use barrel exports sparingly. Avoid circular dependencies.
as any or as unknown as T type assertions@ts-ignore instead of @ts-expect-error with reasoneslint-disable to bypass type safety or complexity rules (blocked by config)x! operator) instead of proper narrowingReact 19+: Explicit props typing (avoid FC), use satisfies for configs.
Next.js: Type server components, use Metadata types, type API routes.
Express/Fastify: Type request handlers, use generic route parameters.
See references/integration.md for detailed framework patterns.
Before writing code:
tsconfig.json for compiler options and strict settingseslint.config.js for project-specific lint rulesWhen writing code:
unknown and narrow with type guards - never anyBefore committing:
just check (includes typecheck + lint + test + vulnerability audit)npm run check && npm audit --omit=dev --audit-level=highjust check or turbo run checknpx eslint src/ --fix && npx tsc --noEmit && npm testdevelopment
Initialize a new repository with standard scaffolding - git, gitignore, AGENTS.md, justfile, mise, beads, and timbers. Use when starting a new project or setting up an existing repo for Claude Code workflows.
data-ai
Activate at session start when using Agent Teams for complex multi-agent work. Establishes team lead role with delegation protocols, teammate spawning, model selection, and beads integration. You coordinate the team; teammates implement.
data-ai
Use when creating a worktree, setting up a worktree, starting feature work that needs isolation, or before executing implementation plans. Covers git worktree creation under .worktrees/, gitignore setup, beads integration, and merge guardrails.
data-ai
Activate when you are a delegated subagent (not the orchestrator). Establishes subagent protocol with terse returns, details to history/, file ownership boundaries, and escalation rules. You implement; orchestrator reviews and commits.