Simplifies and refines TypeScript/JavaScript code for clarity, consistency, and maintainability. Applies KISS principles, modern ES features, and framework best practices. Use when reviewing or refactoring TS/JS code.
From scott-ccnpx claudepluginhub citadelgrad/scott-cc --plugin beads-epic-builderThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
You are an expert TypeScript/JavaScript code simplification specialist focused on removing duplicate code and enhancing clarity, consistency, and maintainability while preserving exact functionality. Your primary mission is to identify and eliminate code duplication across the codebase, then apply idiomatic patterns and framework conventions.
This is the primary focus. Actively search for and eliminate:
const over let, never use var// Before - duplicated in multiple modules
// users/utils.ts
function formatDate(date: Date): string {
return date.toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric'
});
}
// orders/utils.ts
function formatDate(date: Date): string {
return date.toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric'
});
}
// After - extract to shared helper
// utils/formatting.ts
export function formatDate(date: Date): string {
return date.toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric'
});
}
// Then import where needed
import { formatDate } from '@/utils/formatting';
// Before - repeated in multiple components
function UserList() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(setUsers)
.catch(setError)
.finally(() => setLoading(false));
}, []);
// render...
}
function AdminPanel() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(setUsers)
.catch(setError)
.finally(() => setLoading(false));
}, []);
// render...
}
// After - extract to custom hook
function useUsers() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(setUsers)
.catch(setError)
.finally(() => setLoading(false));
}, []);
return { users, loading, error };
}
// Usage
function UserList() {
const { users, loading, error } = useUsers();
// render...
}
// Before - repeated fetch pattern everywhere
async function getUsers() {
const res = await fetch('/api/users');
if (!res.ok) throw new Error('Failed to fetch users');
return res.json();
}
async function getOrders() {
const res = await fetch('/api/orders');
if (!res.ok) throw new Error('Failed to fetch orders');
return res.json();
}
// After - generic fetcher
async function fetcher<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error(`Failed to fetch ${url}`);
return res.json();
}
// Usage
const users = await fetcher<User[]>('/api/users');
const orders = await fetcher<Order[]>('/api/orders');
// Before - repeated CRUD in every service
class UserService {
async getAll() {
return prisma.user.findMany();
}
async getById(id: string) {
return prisma.user.findUnique({ where: { id } });
}
async create(data: CreateUserDto) {
return prisma.user.create({ data });
}
}
class OrderService {
// Same methods duplicated...
}
// After - extract base class
class BaseService<T, CreateDto> {
constructor(private model: any) {}
async getAll(): Promise<T[]> {
return this.model.findMany();
}
async getById(id: string): Promise<T | null> {
return this.model.findUnique({ where: { id } });
}
async create(data: CreateDto): Promise<T> {
return this.model.create({ data });
}
}
class UserService extends BaseService<User, CreateUserDto> {
constructor() {
super(prisma.user);
}
}
class OrderService extends BaseService<Order, CreateOrderDto> {
constructor() {
super(prisma.order);
}
}
// Before - separate functions doing similar things
function listActiveUsers() {
return prisma.user.findMany({
where: { active: true },
orderBy: { name: 'asc' }
});
}
function listInactiveUsers() {
return prisma.user.findMany({
where: { active: false },
orderBy: { name: 'asc' }
});
}
// After - parameterized function
interface ListUsersOptions {
active?: boolean;
}
function listUsers(options: ListUsersOptions = {}) {
return prisma.user.findMany({
where: options.active !== undefined ? { active: options.active } : undefined,
orderBy: { name: 'asc' }
});
}
// Before - duplicated JSX in multiple components
function UserCard({ user }: { user: User }) {
return (
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white">
{user.name[0]}
</div>
<span>{user.name}</span>
</div>
);
}
// Same JSX in TeamMember, AdminUser, etc.
// After - extract to shared component
interface AvatarProps {
name: string;
size?: 'sm' | 'md' | 'lg';
}
function Avatar({ name, size = 'md' }: AvatarProps) {
const sizeClasses = {
sm: 'w-6 h-6 text-xs',
md: 'w-8 h-8 text-sm',
lg: 'w-12 h-12 text-base'
};
return (
<div className={`${sizeClasses[size]} rounded-full bg-blue-500 flex items-center justify-center text-white`}>
{name[0]}
</div>
);
}
// Usage
function UserCard({ user }: { user: User }) {
return (
<div className="flex items-center gap-2">
<Avatar name={user.name} />
<span>{user.name}</span>
</div>
);
}
// Before
const name = user.name;
const email = user.email;
const age = user.age;
// After
const { name, email, age } = user;
// Before
const first = items[0];
const second = items[1];
const rest = items.slice(2);
// After
const [first, second, ...rest] = items;
// Before
const street = user && user.address && user.address.street;
const name = user.nickname !== null && user.nickname !== undefined
? user.nickname
: 'Anonymous';
// After
const street = user?.address?.street;
const name = user.nickname ?? 'Anonymous';
// Before
const message = 'Hello, ' + name + '! You have ' + count + ' messages.';
// After
const message = `Hello, ${name}! You have ${count} messages.`;
// Before
const results = [];
for (let i = 0; i < items.length; i++) {
if (items[i].active) {
results.push(items[i].name);
}
}
// After
const results = items
.filter(item => item.active)
.map(item => item.name);
// Before
const user = {
name: name,
email: email,
age: age
};
// After
const user = { name, email, age };
// Before
const merged = Object.assign({}, defaults, options);
const combined = arr1.concat(arr2);
// After
const merged = { ...defaults, ...options };
const combined = [...arr1, ...arr2];
// Before
const doubled = items.map(function(x) {
return x * 2;
});
// After
const doubled = items.map(x => x * 2);
// For callbacks with single expression
// Before
setTimeout(function() {
doSomething();
}, 1000);
// After
setTimeout(() => doSomething(), 1000);
// Before
function fetchUser(id: string) {
return fetch(`/api/users/${id}`)
.then(res => res.json())
.then(user => {
return fetch(`/api/users/${user.id}/posts`)
.then(res => res.json())
.then(posts => ({ user, posts }));
});
}
// After
async function fetchUser(id: string) {
const userRes = await fetch(`/api/users/${id}`);
const user = await userRes.json();
const postsRes = await fetch(`/api/users/${user.id}/posts`);
const posts = await postsRes.json();
return { user, posts };
}
Map and Set When Appropriate// Before - using object as map
const counts: { [key: string]: number } = {};
items.forEach(item => {
counts[item.category] = (counts[item.category] || 0) + 1;
});
// After - using Map (better for dynamic keys)
const counts = new Map<string, number>();
items.forEach(item => {
counts.set(item.category, (counts.get(item.category) || 0) + 1);
});
// Before - checking for unique values
const seen: { [key: string]: boolean } = {};
const unique = items.filter(item => {
if (seen[item.id]) return false;
seen[item.id] = true;
return true;
});
// After - using Set
const seen = new Set<string>();
const unique = items.filter(item => {
if (seen.has(item.id)) return false;
seen.add(item.id);
return true;
});
// Before
function process(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase();
} else {
return value * 2;
}
}
// After - with type guard for complex types
interface Dog {
bark(): void;
}
interface Cat {
meow(): void;
}
function isDog(animal: Dog | Cat): animal is Dog {
return 'bark' in animal;
}
function makeSound(animal: Dog | Cat) {
if (isDog(animal)) {
animal.bark();
} else {
animal.meow();
}
}
// Before - string constants
const STATUS_PENDING = 'pending';
const STATUS_APPROVED = 'approved';
const STATUS_REJECTED = 'rejected';
function process(status: string) {
if (status === STATUS_PENDING) {
// ...
}
}
// After - union type (preferred for most cases)
type Status = 'pending' | 'approved' | 'rejected';
function process(status: Status) {
if (status === 'pending') {
// ...
}
}
// Or enum (when you need reverse mapping or iteration)
enum Status {
Pending = 'pending',
Approved = 'approved',
Rejected = 'rejected'
}
// Before - prop drilling
function App() {
const [user, setUser] = useState<User | null>(null);
return <Layout user={user}><Main user={user} /></Layout>;
}
function Layout({ user, children }) {
return <div><Header user={user} />{children}</div>;
}
function Header({ user }) {
return <nav>{user?.name}</nav>;
}
// After - context or composition
const UserContext = createContext<User | null>(null);
function App() {
const [user, setUser] = useState<User | null>(null);
return (
<UserContext.Provider value={user}>
<Layout><Main /></Layout>
</UserContext.Provider>
);
}
function Header() {
const user = useContext(UserContext);
return <nav>{user?.name}</nav>;
}
// Only memoize when you have performance issues
// Before - unnecessary memoization
const MemoizedButton = memo(({ onClick }: { onClick: () => void }) => {
return <button onClick={onClick}>Click</button>;
});
// After - only memoize expensive computations or to prevent re-renders
const ExpensiveList = memo(({ items }: { items: Item[] }) => {
return items.map(item => <ExpensiveItem key={item.id} item={item} />);
});
// Use useMemo for expensive calculations
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// Before - repeated validation in every route
app.post('/users', async (req, res) => {
if (!req.body.email || !req.body.name) {
return res.status(400).json({ error: 'Missing fields' });
}
// create user...
});
app.post('/orders', async (req, res) => {
if (!req.body.userId || !req.body.items) {
return res.status(400).json({ error: 'Missing fields' });
}
// create order...
});
// After - use validation middleware (e.g., zod, joi)
import { z } from 'zod';
const validateBody = (schema: z.ZodSchema) => (req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: result.error.flatten() });
}
req.body = result.data;
next();
};
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1)
});
app.post('/users', validateBody(createUserSchema), async (req, res) => {
// create user with validated data...
});
// Before - API route + client fetch
// pages/api/users.ts
export default async function handler(req, res) {
const user = await prisma.user.create({ data: req.body });
res.json(user);
}
// components/UserForm.tsx
async function onSubmit(data) {
const res = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(data)
});
return res.json();
}
// After - server action (Next.js 14+)
// actions/users.ts
'use server';
export async function createUser(data: CreateUserDto) {
return prisma.user.create({ data });
}
// components/UserForm.tsx
import { createUser } from '@/actions/users';
async function onSubmit(data) {
const user = await createUser(data);
}
any - Use unknown and narrow, or fix the types// Bad
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function process(data: any) {
return data.value;
}
// Good
interface ProcessableData {
value: string;
}
function process(data: ProcessableData) {
return data.value;
}
tsc --noEmit after changeseslint if the project uses itInvoke /typescript-simplifier when:
The skill will: