git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/cleanup-dead-code" ~/.claude/skills/intense-visions-harness-engineering-cleanup-dead-code-002633 && rm -rf "$T"
agents/skills/claude-code/cleanup-dead-code/SKILL.mdCleanup Dead Code
Entropy analysis and safe cleanup. Find unused exports, dead files, and pattern violations — then remove them without breaking anything.
When to Use
- After a major refactoring or feature removal
- When the codebase feels "heavy" — too many files, unclear what is used
- As a periodic hygiene task (monthly or per-quarter)
- When
triggers fireon_entropy_check - After deleting a feature flag and its associated code paths
- When
reports growing entropy in the project health dashboardharness cleanup - NOT during active feature development — dead code cleanup is a separate, focused task
- NOT when tests are failing — fix tests first, then clean up
- NOT under time pressure — dead code removal requires careful verification
Process
Phase 1: Analyze — Run Entropy Report
-
Run
to generate a full entropy report. This analyzes:harness cleanup- Dead exports — functions, classes, and constants that are exported but never imported anywhere
- Unused files — files that are not imported by any other file in the project
- Pattern violations — code that violates established project patterns (e.g., a service file without a corresponding test file)
- Orphaned dependencies — npm packages listed in package.json but never imported
-
Run
for machine-readable output focused specifically on unused code.harness cleanup --type dead-code --json -
Review the report summary. Note the total count and distribution. A few dead exports are normal; dozens suggest accumulated entropy from incomplete refactorings.
Graph-Enhanced Context (when available)
When a knowledge graph exists at
.harness/graph/, use graph queries for faster, more accurate dead code detection:
— perform graph reachability analysis from entry point nodes to identify truly unreachable codequery_graph
— distinguish dynamic imports from genuinely unused code by checking edge typesget_relationships
Graph reachability reduces false positives compared to static analysis alone, since it traces the full transitive import graph including re-exports. Fall back to file-based commands if no graph is available.
Phase 2: Categorize — Safe vs. Needs Review
Safe to auto-fix (remove without further analysis):
- Exports that are not imported anywhere AND not referenced in any config file
- Files that are not imported anywhere AND have no side effects (no top-level execution)
- Dead exports (non-public, zero importers) -- remove
keyword or delete entirely if zero internal callersexport - Commented-out code blocks -- delete commented block (dead by definition, code is in git history)
- Orphaned npm dependencies -- remove from package.json (probably safe; needs install+test verification)
- Unused local variables and imports within a file (linter can catch these)
Needs human review before removal:
- Exports that APPEAR unused but might be consumed dynamically (see "What NOT to Delete" below)
- Files that appear unused but are entry points (CLI scripts, test fixtures, config files)
- Code that is only used in specific build configurations or environments
- Exports from a public package — external consumers may depend on them
Phase 3: Apply Safe Fixes
For each item categorized as safe:
-
Remove the dead code. Delete the export, function, file, or import.
-
Run
to update any documentation references that pointed to the deleted code.harness fix-drift -
Run the full test suite. All tests must pass. If any test fails:
- STOP. The code was not actually dead — something depends on it.
- Undo the deletion immediately.
- Reclassify the item as "needs human review" and investigate the hidden dependency.
-
Run
andharness validate
. Both must pass.harness check-deps -
Commit the cleanup. Group related removals into logical commits (e.g., "remove unused shipping utilities" not "delete dead code").
New fix types:
- Dead exports (non-public): Use
withdetect_entropy
. The tool removes theautoFix: true, fixTypes: ['dead-exports']
keyword. If the function/class has zero internal callers too, delete the entire declaration.export - Commented-out code: Use
withdetect_entropy
. The tool deletes commented-out code blocks. This is cosmetic and only needs lint verification.autoFix: true, fixTypes: ['commented-code'] - Orphaned dependencies: Use
withdetect_entropy
. The tool removes the dep from package.json. Must runautoFix: true, fixTypes: ['orphaned-deps']
after to verify nothing breaks.pnpm install && pnpm test
Phase 3.5: Convergence Loop (Standalone)
When running standalone (not through the orchestrator), apply a single-concern convergence loop:
- Re-run detection. After applying all safe fixes, run
again.harness cleanup --type dead-code - Check if issue count decreased. Compare the new count to the previous count.
- If decreased: loop. New dead code may have been exposed by the fixes (e.g., removing a dead export made a file fully unused). Go back to Phase 2 (Categorize) with the new report.
- If unchanged: stop. No more cascading fixes are possible. Proceed to Phase 4 (Report).
- Maximum iterations: 5. To prevent infinite loops, stop after 5 convergence cycles regardless.
Why convergence matters: Removing dead code can create more dead code. For example:
- Removing a dead export may make all remaining exports in a file dead, making the file itself dead.
- Removing a dead file removes its imports, which may make other files' exports dead.
- Removing an orphaned dep may cause lint warnings that reveal unused imports.
Phase 4: Report Remaining Items
Graph Refresh
If a knowledge graph exists at
.harness/graph/, refresh it after code changes to keep graph queries accurate:
harness scan [path]
Dead code removal changes graph topology — skipping this step means subsequent graph queries (impact analysis, dependency health, test advisor) may return stale results.
For items that need human review, report:
- What it is — file path, export name, what it does
- Why it appears dead — no static imports found
- Why it might not be dead — dynamic import patterns, external consumers, test utilities
- Recommended action — delete with confidence, delete with caution, or keep
What NOT to Delete
These patterns make code APPEAR dead when it is actually in use:
Dynamically imported modules
// This import won't show up in static analysis const module = await import(`./plugins/${pluginName}`);
Files in a
plugins/ directory may appear unused but are loaded dynamically at runtime. Check for import() calls, require() with variables, and glob-based loading patterns.
Test utilities and fixtures
// test-helpers.ts — only imported by test files export function createMockUser() { ... }
Test utility files may appear unused if the analysis excludes test files. Verify that
harness cleanup includes test files in its import graph.
Type-only exports
// Only used as a type, never as a value export interface UserConfig { ... }
Some analysis tools miss type-only imports (
import type { UserConfig }). Verify before deleting any exported interface or type alias.
Package entry points
// index.ts — re-exports for external consumers export { createClient } from './client';
The entry point of a package re-exports things for external consumers. These exports may have zero internal imports but are the public API.
Side-effect files
// polyfills.ts — imported for side effects, not for exports import './polyfills';
Some files are imported for their side effects (polyfills, global registrations, CSS). They have no exports but are not dead.
Environment-specific code
// Only used when FEATURE_FLAG_X is enabled if (process.env.FEATURE_FLAG_X) { const handler = require('./experimental-handler'); }
Code behind feature flags or environment checks may appear dead in the default configuration.
Harness Integration
— Full entropy analysis. Reports dead exports, unused files, pattern violations, and orphaned dependencies.harness cleanup
— Focused analysis on unused code specifically.harness cleanup --type dead-code
— Machine-readable output for automation.harness cleanup --type dead-code --json
— Update documentation after removing dead code. Ensures deleted items are not still referenced in docs.harness fix-drift
— Run after cleanup to verify project structure remains valid.harness validate
— Run after cleanup to verify no dependency violations were introduced.harness check-deps
Success Criteria
reports zero dead exports and zero unused files (or all remaining items are documented as intentional)harness cleanup- All tests pass after every deletion
andharness validate
pass after cleanupharness check-deps- Documentation references to deleted code are updated or removed
- No dynamically-imported, type-only, or side-effect code was accidentally deleted
- Each cleanup commit is atomic and has a descriptive message explaining what was removed and why
Rationalizations to Reject
These are common rationalizations that sound reasonable but lead to incorrect results. When you catch yourself thinking any of these, stop and follow the documented process instead.
| Rationalization | Why It Is Wrong |
|---|---|
| "This export has zero static imports, so it is definitely dead and safe to remove" | Zero static imports does not mean zero consumers. Dynamic imports, type-only imports, side-effect imports, and package entry points all create false positives. |
| "I removed the dead code and the tests pass, so I do not need to run harness validate and check-deps" | Both harness validate and harness check-deps must pass after every cleanup. Dead code removal can introduce dependency violations. |
| "The convergence loop found new dead code after my fixes, but it is probably just noise from the tool" | Removing dead code creates more dead code. The convergence loop exists to catch these cascades. If the issue count decreased, loop back. |
| "The entropy report has 60 items but I can clean them all up in one pass to be thorough" | When the report is very large (>50 items), pick the highest-confidence dead code first. Attempting everything at once risks compound errors. |
Examples
Example: Removing unused utility functions
Entropy report:
DEAD EXPORT: src/utils/string-helpers.ts - capitalizeFirst() — 0 imports found - truncateWithEllipsis() — 0 imports found - slugify() — 3 imports found (ACTIVE, not dead)
Action: Remove
capitalizeFirst and truncateWithEllipsis from the file. Keep slugify. Run tests — all pass. Commit: "remove unused capitalizeFirst and truncateWithEllipsis from string-helpers"
Example: Detecting a false positive
Entropy report:
UNUSED FILE: src/plugins/markdown-renderer.ts - 0 static imports found - File contains: export default class MarkdownRenderer
Investigation: Check for dynamic imports:
// src/plugins/index.ts const renderer = await import(`./${format}-renderer`);
Result: False positive. The file is loaded dynamically based on the
format variable. Mark as intentional and add a comment in the file explaining the dynamic loading pattern.
Example: Orphaned npm dependency
Entropy report:
ORPHANED DEPENDENCY: moment (package.json) - 0 imports of 'moment' found in src/ - Last imported in commit abc123 (removed 3 months ago)
Action: Remove
moment from package.json dependencies. Run npm install to update lockfile. Run tests — all pass. Commit: "remove unused moment dependency"
Escalation
- When removing code causes unexpected test failures: The code has a hidden dependency. Undo the deletion, investigate the dependency chain, and document the finding. The code is not dead — it is just hard to trace.
- When the entropy report is very large (>50 items): Do not attempt to clean everything at once. Pick the highest-confidence dead code (files with zero imports, no dynamic patterns) and clean those first. Schedule the rest across multiple sessions.
- When you are unsure if an export is used externally: Check if the code is in a published package. If so, removing an export is a breaking change. Deprecate first, then remove in a major version.
- When dead code is tangled with live code in the same file: Extract the live code first (using harness-refactoring), then delete the remaining dead code. Do not try to surgically remove dead code from a complex file in one step.