From typescript-pragmatic-programmer
Pragmatic programming principles and wisdom applied to TypeScript development. Use this skill when writing TypeScript code to follow pragmatic practices for maintainable, robust software.
How this skill is triggered — by the user, by Claude, or both
Slash command
/typescript-pragmatic-programmer:typescript-pragmatic-programmerThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Practical wisdom for TypeScript developers, focusing on pragmatic approaches to building maintainable, robust software.
Practical wisdom for TypeScript developers, focusing on pragmatic approaches to building maintainable, robust software.
Make small, incremental changes and verify them immediately. After every change, run TypeScript compilation and tests before moving forward. This tight feedback loop catches errors early when they're easiest to fix and prevents you from building on faulty foundations.
Large changes introduce many variables, making it hard to identify what went wrong when something breaks. Small changes with immediate verification create a safety net that catches problems at their source. The cost of fixing a bug grows exponentially with the time between introduction and detection.
After every meaningful change, no matter how small:
tsc or your build tool to catch type errors# After each change
npm run build # Verify TypeScript compilation
npm test # Verify tests pass
git add .
git commit -m "Small, verified change"
tsc --watch and npm test -- --watch for instant feedbackBuild a thin slice through all layers of your system first, then expand it. This ensures all parts integrate correctly before investing in features. A "tracer bullet" hits the target, showing you're aimed correctly.
When building a new feature:
// Step 1: Minimal end-to-end (tracer bullet)
app.post('/api/users', async (c) => {
const user = await db.users.create({ name: 'test' });
return c.json(user);
});
// Step 2: Add validation
app.post('/api/users', async (c) => {
const data = CreateUserSchema.parse(await c.req.json());
const user = await db.users.create(data);
return c.json(user);
});
// Step 3: Add service layer
app.post('/api/users', async (c) => {
const data = CreateUserSchema.parse(await c.req.json());
const user = await userService.createUser(data);
return c.json(user);
});
Before implementing functionality yourself, check if the user has expressed a preference for a suitable library. If not, search for well-maintained, high-quality libraries on npm. Reusing proven solutions saves time, reduces bugs, and gives you battle-tested code that handles edge cases you might miss.
Writing custom implementations is expensive: you pay for development time, testing time, bug fixes, maintenance, and opportunity cost. Good libraries have already paid these costs and spread them across thousands of users. Your time is better spent on domain-specific problems that differentiate your application.
When you need common functionality:
Write custom implementations only when:
Math.max(a, b))When recommending libraries, consider:
When you encounter a problem while implementing the user's design, fix the root issue rather than abandoning the approach or disabling safeguards. If the user has specified a design pattern or architecture, honor it even when it creates challenges. Taking shortcuts—like disabling TypeScript, turning off tests, or ignoring lint rules—creates technical debt and undermines the quality foundation.
Quality tools like TypeScript, tests, and linters exist to catch problems. When they complain, they're doing their job. Disabling them is shooting the messenger. Similarly, if the user has chosen an architectural approach (events, dependency injection, etc.), they did so for good reasons. Abandoning it at the first sign of difficulty shows lack of discipline and understanding.
Wrong Response:
any"Right Response:
any or @ts-ignoreUse docker-compose to manage local backing services like databases, Redis, message queues, etc. This ensures consistent environments across developers and CI, eliminates "works on my machine" problems, and provides isolation between development and test databases.
docker-compose up instead of installing databasesdocker-compose down -v removes all data for fresh startsRun separate database containers for development and tests. This provides complete isolation and allows both environments to run simultaneously.
# docker-compose.yml
services:
postgres-dev:
image: postgres:16-alpine
container_name: my-app-postgres-dev
environment:
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- postgres-dev-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
postgres-test:
image: postgres:16-alpine
container_name: my-app-postgres-test
environment:
POSTGRES_PASSWORD: postgres
ports:
- "5433:5432" # Different host port
tmpfs:
- /var/lib/postgresql/data # In-memory for faster tests
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
postgres-dev-data:
Note: Since each container is dedicated to a single application, it's fine to use the default postgres user and postgres database name. There's no need to create custom database names when you have complete container isolation.
{
"scripts": {
"docker:up": "docker-compose up -d",
"docker:down": "docker-compose down",
"docker:reset": "docker-compose down -v && docker-compose up -d",
"docker:logs": "docker-compose logs -f"
}
}
latest tag (e.g., postgres:16-alpine, not postgres:latest)npx claudepluginhub cressie176/cressie176-claude-marketplace --plugin typescript-pragmatic-programmerApplies Clean Code principles and professional practices to TypeScript projects: code review, refactoring, TDD, bug fixing, task estimation, and team collaboration.
Guides usage of TypeScript language features, build tools, package managers, project structure, and type system. For choosing runtimes, configuring tsconfig, publishing packages, and building CLIs.
TypeScript deep-dive skill covering type-level programming, performance optimization, monorepo patterns, and migration strategies. Also redirects to specialized subagents for bundler/module/type issues.