From workflow-skills
Identifies and removes dead/orphaned code, tests, and Storybook stories — including trivial tests that check only obvious behavior or rare edge cases with no real value. Always validates that code is truly unused before deleting anything. Trigger on: "remove dead code", "clean up unused files", "delete orphaned components", "find unused exports", "remove trivial tests", "clean up the codebase", "what code can we delete", "prune the repo", "remove stale stories".
How this skill is triggered — by the user, by Claude, or both
Slash command
/workflow-skills:dead-code-cleanupThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Systematically identifies dead, orphaned, and trivial code across the codebase including components, tests, and Storybook stories. Validates that code is truly unused before removal to prevent breaking changes. Also removes stories that provide no documentation value or only demonstrate rare edge cases.
Systematically identifies dead, orphaned, and trivial code across the codebase including components, tests, and Storybook stories. Validates that code is truly unused before removal to prevent breaking changes. Also removes stories that provide no documentation value or only demonstrate rare edge cases.
Examples of trivial stories to remove:
// ❌ Trivial - just default with no props (when Default story exists)
export const Empty: Story = {
args: {},
};
// ❌ Trivial - rare edge case nobody will encounter
export const WithExactly47Characters: Story = {
args: {
text: "This string has exactly forty-seven chars!",
},
};
// ❌ Trivial - duplicate of existing state
export const LoadingState: Story = {
args: { loading: true },
};
export const IsLoading: Story = { // Duplicate!
args: { loading: true },
};
// ❌ Trivial - no documentation value
export const WithPadding5: Story = {
args: { padding: 5 },
};
export const WithPadding6: Story = {
args: { padding: 6 },
};
export const WithPadding7: Story = {
args: { padding: 7 },
};
// ✅ Keep - demonstrates important state
export const ErrorState: Story = {
args: {
error: "Failed to load data",
onRetry: fn(),
},
};
// ✅ Keep - shows important variant
export const WithLongContent: Story = {
args: {
children: "Very long content that tests text wrapping...",
},
};
For Components:
# Find all component files
find src/components -name "*.tsx" -o -name "*.ts"
# For each component, check for imports
grep -r "import.*ComponentName" src/
grep -r "from.*path/to/component" src/
For Tests:
# Find all test files
find . -name "*.test.ts" -o -name "*.test.tsx"
# For each test, verify the tested file exists
# Check if test is testing actual behavior (not just rendering)
For Storybook Stories:
# Find all story files
find . -name "*.stories.tsx" -o -name "*.stories.ts"
# For each story:
# 1. Verify the component exists (orphaned check)
# 2. Check if story provides meaningful documentation
# 3. Look for duplicate states
# 4. Identify edge case stories
Before marking as dead, verify:
For tests, verify they're trivial:
// Trivial test patterns
❌ test('renders without crashing', () => { render(<Component />) })
❌ test('exists', () => { expect(Component).toBeDefined() })
❌ test('has correct snapshot', () => { expect(tree).toMatchSnapshot() })
❌ test('renders', () => { render(<Component />); expect(screen.getByRole('button')).toBeInTheDocument() })
// Non-trivial tests
✅ test('displays error when validation fails', () => { ... })
✅ test('submits form with correct data', () => { ... })
✅ test('calls callback when button clicked', () => { ... })
For stories, verify they're trivial:
// Read the story file and check:
// 1. Does it duplicate another story's state?
// 2. Is it an edge case (e.g., exactly N characters, rare prop combination)?
// 3. Does it show obvious behavior?
// 4. Is it just testing every prop value without value?
// Compare stories:
const stories = extractStoriesFromFile(file);
const duplicates = findDuplicateStates(stories);
const edgeCases = findEdgeCaseStories(stories);
const noValue = findNoValueStories(stories);
Organize findings by confidence level:
High confidence (safe to remove):
Medium confidence (needs review):
Low confidence (keep or investigate):
For each Storybook story, evaluate:
Does it show a distinct visual state?
Would a developer need to see this?
Does it document important behavior?
Is it already covered by another story?
Is this a realistic use case?
Common trivial story patterns:
// Pattern 1: Duplicate states
export const Loading = { args: { isLoading: true } };
export const IsLoading = { args: { isLoading: true } }; // ❌ Remove
// Pattern 2: Micro-variations
export const Small = { args: { size: 'sm' } };
export const SmallWithPadding = { args: { size: 'sm', padding: 2 } }; // ❌ Remove if not meaningful
// Pattern 3: Edge cases
export const With99Items = { args: { items: Array(99).fill({}) } }; // ❌ Remove (why 99?)
// Pattern 4: Every prop value
export const RedButton = { args: { color: 'red' } };
export const BlueButton = { args: { color: 'blue' } };
export const GreenButton = { args: { color: 'green' } };
// ❌ Keep one or two examples, remove the rest
// Pattern 5: No-value empty states
export const Empty = { args: {} }; // ❌ Remove if Default exists
Keep these valuable stories:
// ✅ Shows important interactive state
export const WithValidationError: Story = {
args: {
error: "Email is required",
touched: true,
},
};
// ✅ Demonstrates realistic use case
export const LongFormWithManyFields: Story = {
args: {
fields: realisticFormFields,
},
};
// ✅ Shows responsive behavior
export const MobileLayout: Story = {
parameters: {
viewport: { defaultViewport: 'mobile1' },
},
};
// ✅ Documents complex interaction
export const WithAsyncValidation: Story = {
args: {
onValidate: async (value) => { /* realistic validation */ },
},
play: async ({ canvasElement }) => {
// Demonstrates the interaction
},
};
Document all findings:
## Dead Code Findings
### High Confidence (Safe to Remove)
- [ ] `src/components/OldButton.tsx` - No imports, replaced by shadcn Button
- [ ] `src/components/__tests__/OldButton.test.tsx` - Test for removed component
- [ ] `src/stories/OldButton.stories.tsx` - Story for removed component
### Trivial Tests to Remove
- [ ] `Component.test.tsx:15-18` - "renders without crashing" test
- [ ] `Component.test.tsx:20-23` - Empty snapshot test
- [ ] `Form.test.tsx:45-48` - Duplicate of existing test
### Trivial Stories to Remove
- [ ] `Button.stories.tsx` - "Empty" story (duplicates Default)
- [ ] `Button.stories.tsx` - "WithPadding5/6/7" stories (no value)
- [ ] `Input.stories.tsx` - "With47Characters" story (contrived edge case)
- [ ] `Card.stories.tsx` - "Loading" and "IsLoading" (duplicates)
### Medium Confidence (Review First)
- [ ] `src/lib/oldDateFormatter.ts` - Only used in dead code
- [ ] `Dialog.stories.tsx` - "WithCustomZIndex" (edge case?)
Present findings for approval:
Found the following dead/trivial code:
High Confidence (3 files, 245 lines):
- OldButton component and tests (never imported)
- Legacy date formatter (only used in removed code)
Trivial Tests (12 tests, 156 lines):
- 8 "renders without crashing" tests
- 4 duplicate tests
Trivial Stories (15 stories, 203 lines):
- 6 stories showing micro-variations (padding values)
- 4 duplicate state stories
- 3 contrived edge case stories
- 2 empty/default duplicates
Total potential cleanup: 5 files + 27 test/story removals, 604 lines
Shall I proceed with removal?
Remove in order:
For each removal:
# 1. For code: Double-check no imports
grep -r "ComponentName" src/
# 2. For stories: Verify not referenced in docs
grep -r "story-id" docs/
# 3. Check git history to understand why it existed
git log --all --full-history -- path/to/file.tsx
# 4. Remove file or specific exports
# Option A: Remove entire file
rm path/to/file.tsx
# Option B: Remove specific story/test from file
# Use Edit tool to remove the export
# 5. Verify build still works
npm run build
# 6. Verify tests still pass
npm run test
# 7. Verify Storybook builds
npm run storybook
Create focused commits:
# Separate commits for different types
git commit -m "test: Remove trivial 'renders without crashing' tests"
git commit -m "test: Remove duplicate test cases"
git commit -m "docs: Remove trivial Storybook stories with no documentation value"
git commit -m "docs: Remove edge case stories (WithExactly47Chars, etc)"
git commit -m "test: Remove orphaned tests for deleted components"
git commit -m "docs: Remove orphaned Storybook stories"
git commit -m "refactor: Remove dead OldButton component"
git commit -m "refactor: Remove debug utilities and commented code"
Find components with no imports:
# For each .tsx component file
for file in $(find src/components -name "*.tsx" ! -name "*.test.tsx" ! -name "*.stories.tsx"); do
name=$(basename "$file" .tsx)
imports=$(grep -r "import.*$name" src/ | wc -l)
if [ "$imports" -eq 0 ]; then
echo "No imports: $file"
fi
done
Find trivial tests:
# Search for common trivial test patterns
grep -r "renders without crashing" src/
grep -r "toBeDefined()" src/ | grep -i component
grep -r "toMatchSnapshot()" src/ | grep -v "// meaningful snapshot"
grep -r "test('renders'" src/
grep -r "it('renders'" src/
Find potentially trivial stories:
# Search for common trivial story patterns
grep -r "export const Empty" **/*.stories.tsx
grep -r "args: {}" **/*.stories.tsx
grep -r "WithExactly" **/*.stories.tsx
grep -r "With[0-9]+" **/*.stories.tsx
# Find story files with many exports (likely over-specified)
for file in $(find . -name "*.stories.tsx"); do
count=$(grep -c "^export const.*Story" "$file")
if [ "$count" -gt 10 ]; then
echo "Many stories ($count): $file"
fi
done
Find orphaned stories:
# For each story file, check if component exists
for story in $(find . -name "*.stories.tsx"); do
# Extract component import
component=$(grep "import.*from" "$story" | grep -v "@" | head -1)
# Check if imported file exists
# ...validation logic...
done
Required validations:
npm run testnpm run buildnpm run storybookgrep -r "import(" src/grep -r "ComponentName" src/Verification steps:
npm run testnpm run buildnpm run storybooknpm run type-check (if available)npm run lintProblem: Deleted a component but forgot to remove tests/stories
Solution:
# Find the component in git history
git log --all --full-history --diff-filter=D -- "**/ComponentName.tsx"
# Find orphaned test
find . -name "*ComponentName.test.tsx"
# Find orphaned story
find . -name "*ComponentName.stories.tsx"
# Remove both
rm path/to/ComponentName.test.tsx
rm path/to/ComponentName.stories.tsx
Problem: Some stories are valuable, others are trivial variations
Detection:
// src/stories/Button.stories.tsx
// ✅ Keep - shows important states
export const Default: Story = { args: { children: "Click me" } };
export const Disabled: Story = { args: { disabled: true } };
export const Loading: Story = { args: { loading: true } };
// ❌ Remove - trivial variations
export const WithPadding5: Story = { args: { padding: 5 } };
export const WithPadding6: Story = { args: { padding: 6 } };
export const WithPadding7: Story = { args: { padding: 7 } };
// ❌ Remove - duplicate
export const IsLoading: Story = { args: { loading: true } }; // Same as Loading!
// ❌ Remove - contrived edge case
export const WithExactly42Characters: Story = {
args: { children: "This button has exactly 42 characters!" },
};
Action: Use Edit tool to remove only the trivial exports, keep the valuable ones
Problem: Tests that only check component renders
Detection:
// src/components/__tests__/MyComponent.test.tsx
❌ test('renders without crashing', () => {
render(<MyComponent />);
// No assertions!
});
❌ test('component exists', () => {
expect(MyComponent).toBeDefined();
// Pointless - TypeScript already ensures this
});
❌ test('renders button', () => {
render(<MyComponent />);
expect(screen.getByRole('button')).toBeInTheDocument();
// Just checking it renders, no behavior tested
});
Action: Remove these tests, ensure meaningful behavior tests remain
Problem: Multiple versions of same utility
Detection:
# Find similar function names
grep -r "function formatDate" src/lib/
grep -r "export.*formatDate" src/
# Compare implementations
# Keep best one, remove duplicates
Problem: Console.log, debug flags, test utilities in production code
Detection:
# Find debug statements
grep -r "console.log" src/ | grep -v ".test." | grep -v "// DEBUG:"
grep -r "console.debug" src/
grep -r "debugger;" src/
# Find debug flags
grep -r "DEBUG.*=.*true" src/
grep -r "__DEV__" src/
Even if looks trivial:
Requires extra validation:
Before cleanup:
# Count files
find src -name "*.tsx" -o -name "*.ts" | wc -l
# Count lines of code
find src -name "*.tsx" -o -name "*.ts" | xargs wc -l | tail -1
# Count test files
find src -name "*.test.tsx" -o -name "*.test.ts" | wc -l
# Count stories
find . -name "*.stories.tsx" -exec grep -c "^export const.*Story" {} + | awk '{s+=$1} END {print s}'
After cleanup, report:
Dead Code Cleanup Results:
- Removed: 8 files
- Lines deleted: 1,247
- Trivial tests removed: 12
- Trivial stories removed: 23
- Orphaned tests removed: 5
- Orphaned stories removed: 3
- Build time: 15% faster
- Bundle size: 8KB smaller
- Storybook: 23 fewer stories, clearer documentation
Dead Code Cleanup Process:
Story Evaluation Criteria:
Remember:
The goal: Clean, maintainable codebase with only code that serves a purpose, properly tested with meaningful tests that validate behavior, and Storybook stories that provide actual documentation value for developers.
Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub jerseycheese/agent-skills --plugin workflow-skills