agentleFS
iwritec0deiwritec0de/wp-dev/skills/php-cleanup/SKILL.md

php-cleanup

Skill1 starsChanged 7 months agoMITView on GitHub

This skill should be used when the user asks to "find unused PHP code", "clean up dead code", "find unused Composer dependencies", "find unused imports", "remove dead PHP code", "run composer unused", "check for missing dependencies", "detect unused classes", "detect unused methods", "clean up use statements", "audit PHP dependencies", or mentions "composer-unused", "composer-require-checker", "Psalm unused code", "dead code detection", "unused imports", "unused dependencies", "PHP cleanup", "WordPress dead code". Provides expert guidance on detecting and removing unused code, dependencies, imports, and dead exports in PHP/WordPress projects using composer-unused, composer-require-checker, Psalm, and PHP-CS-Fixer.

The file

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 2 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When cross-referencing Psalm's unused code report against actual hook registrations, search for these patterns:

    line 324
  2. Distinctive:

    Follow this order to avoid cascading issues:

    line 397

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

PHP/WordPress Dead Code & Dependency Cleanup

A toolchain that replicates what Knip does for JS/TS — detect unused code, unused dependencies, missing dependencies, and dead exports — but for PHP/WordPress plugin and theme development.

Toolchain Overview

ToolPurposeDetects
composer-unusedUnused Composer packagesPackages in require/require-dev never referenced in code
composer-require-checkerMissing Composer packagesPackages used in code but not declared in composer.json
Psalm --find-unused-codeDead PHP codeUnused classes, methods, functions, properties, variables
PHP-CS-FixerUnused use importsuse statements at the top of files that aren't referenced
PHP_CodeSnifferUnused imports (alt)Same as above, alternative tool

Installation

All Tools at Once

composer require --dev \
  icanhazstring/composer-unused \
  maglnet/composer-require-checker \
  vimeo/psalm \
  php-stubs/wordpress-stubs \
  friendsofphp/php-cs-fixer

Individual Installation

# Unused dependency detection
composer require --dev icanhazstring/composer-unused

# Missing dependency detection
composer require --dev maglnet/composer-require-checker

# Static analysis + dead code detection
composer require --dev vimeo/psalm
composer require --dev php-stubs/wordpress-stubs

# Unused import cleanup
composer require --dev friendsofphp/php-cs-fixer

Tool 1: composer-unused — Unused Dependencies

Detects Composer packages listed in composer.json that are never referenced anywhere in the codebase.

Usage

# Basic scan
composer unused

# JSON output for parsing
composer unused --output-format=json

# Exclude specific packages from the check
composer unused --excludePackage=php-stubs/wordpress-stubs

# Only check runtime dependencies (skip dev)
composer unused --no-dev

Configuration (composer-unused.php)

<?php

declare(strict_types=1);

use ComposerUnused\ComposerUnused\Configuration\Configuration;
use ComposerUnused\ComposerUnused\Configuration\NamedFilter;
use ComposerUnused\ComposerUnused\Configuration\PatternFilter;

return static function (Configuration $config): Configuration {
    return $config
        // Packages used dynamically or via WordPress hooks
        ->addNamedFilter(NamedFilter::fromString('php-stubs/wordpress-stubs'))
        ->addNamedFilter(NamedFilter::fromString('wpackagist-plugin/some-plugin'))
        // Ignore all packages matching a pattern
        ->addPatternFilter(PatternFilter::fromString('/^ext-/'));
};

Common False Positives in WordPress

These packages are often flagged but are actually used:

  • php-stubs/wordpress-stubs — Type stubs, not runtime code
  • php-stubs/woocommerce-stubs — Same, for WooCommerce
  • Platform extensions (ext-json, ext-mbstring) — Provided by PHP itself
  • WordPress plugins installed via Composer (wpackagist) — May be loaded by WP, not imported directly

Tool 2: composer-require-checker — Missing Dependencies

The inverse of composer-unused: finds symbols (classes, functions, constants) that the code references but aren't declared in composer.json.

Usage

# Basic check
vendor/bin/composer-require-checker check

# Check a specific composer.json
vendor/bin/composer-require-checker check composer.json

# JSON output
vendor/bin/composer-require-checker check --output=json

Configuration (composer-require-checker.json)

{
  "symbol-white-list": [
    "null", "true", "false",
    "static", "self", "parent",
    "array", "string", "int", "float", "bool", "callable", "iterable", "void", "object", "mixed", "never",
    "WP_Query", "WP_Post", "WP_User", "WP_Error", "WP_REST_Request", "WP_REST_Response",
    "WP_REST_Server", "WP_REST_Controller", "WP_Widget", "WP_Customize_Control",
    "wpdb", "WP_Hook", "WP_Screen", "WP_Admin_Bar", "WP_Block", "WP_Block_Type",
    "Walker", "Walker_Nav_Menu", "WP_Filesystem_Base",
    "ABSPATH", "WPINC", "WP_CONTENT_DIR", "WP_PLUGIN_DIR", "WP_DEBUG",
    "add_action", "add_filter", "apply_filters", "do_action",
    "register_activation_hook", "register_deactivation_hook",
    "wp_enqueue_script", "wp_enqueue_style", "wp_localize_script",
    "get_option", "update_option", "delete_option",
    "esc_html", "esc_attr", "esc_url", "wp_kses", "wp_kses_post",
    "sanitize_text_field", "absint", "wp_unslash",
    "wp_nonce_field", "wp_verify_nonce", "check_admin_referer",
    "current_user_can", "is_admin", "is_user_logged_in",
    "__", "_e", "_x", "_n", "esc_html__", "esc_html_e", "esc_attr__", "esc_attr_e"
  ]
}

Critical: WordPress core functions, classes, and constants are globally available at runtime but not declared in composer.json. The whitelist above prevents false positives for the most common WordPress symbols. Extend it as needed for your project.

Tool 3: Psalm — Dead Code Detection

Psalm's --find-unused-code flag performs deep static analysis to detect genuinely unreferenced code.

What Psalm Detects

CategoryExample
Unused classesClasses never instantiated or referenced
Unused methodsMethods never called (including private/protected)
Unused functionsStandalone functions never invoked
Unused propertiesClass properties never read or written
Unused variablesVariables assigned but never used
Unused parametersMethod/function parameters never referenced in the body
Dead code pathsCode after return, unreachable else branches

Configuration (psalm.xml)

<?xml version="1.0"?>
<psalm
    errorLevel="4"
    findUnusedCode="true"
    findUnusedBaselineEntry="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
    <projectFiles>
        <directory name="src" />
        <directory name="includes" />
        <ignoreFiles>
            <directory name="vendor" />
            <directory name="node_modules" />
            <directory name="tests" />
            <directory name="build" />
        </ignoreFiles>
    </projectFiles>

    <stubs>
        <file name="vendor/php-stubs/wordpress-stubs/wordpress-stubs.php" />
    </stubs>

    <plugins>
        <!-- Add if using WooCommerce -->
        <!-- <file name="vendor/php-stubs/woocommerce-stubs/woocommerce-stubs.php" /> -->
    </plugins>
</psalm>

Usage

# Full analysis with unused code detection
vendor/bin/psalm --find-unused-code

# Only report unused code (suppress other errors)
vendor/bin/psalm --find-unused-code --show-info=true

# JSON output for parsing
vendor/bin/psalm --find-unused-code --output-format=json

# Target specific directories
vendor/bin/psalm --find-unused-code src/ includes/

# Generate baseline (accept current state, catch new issues)
vendor/bin/psalm --find-unused-code --set-baseline=psalm-baseline.xml

# Run against baseline
vendor/bin/psalm --find-unused-code --use-baseline=psalm-baseline.xml

Suppressing False Positives

WordPress Hook Callbacks

Functions registered via add_action/add_filter appear unused because they're invoked dynamically by WordPress, not called directly in your code. Suppress per-function:

/** @psalm-suppress PossiblyUnusedMethod */
public function handle_form_submission(): void {
    // Registered via: add_action('admin_post_my_form', [$this, 'handle_form_submission'])
}

Or suppress at the class level for classes that are entirely hook-driven:

/** @psalm-suppress UnusedClass */
class Admin_Ajax_Handler {
    // All methods are registered via add_action('wp_ajax_*', ...)
}

Inline Suppression Reference

/** @psalm-suppress UnusedClass */
/** @psalm-suppress PossiblyUnusedMethod */
/** @psalm-suppress UnusedVariable */
/** @psalm-suppress UnusedProperty */
/** @psalm-suppress UnusedParam */

Baseline Approach (Recommended for Existing Projects)

Rather than suppressing hundreds of existing findings, generate a baseline and only enforce on new code:

vendor/bin/psalm --find-unused-code --set-baseline=psalm-baseline.xml

This creates psalm-baseline.xml with all current findings. Future runs will only report NEW unused code.

Tool 4: PHP-CS-Fixer — Unused Import Cleanup

Detects and auto-removes unused use statements at the top of PHP files.

Configuration (.php-cs-fixer.php)

<?php

$finder = PhpCsFixer\Finder::create()
    ->in([__DIR__ . '/src', __DIR__ . '/includes'])
    ->exclude(['vendor', 'node_modules', 'build']);

return (new PhpCsFixer\Config())
    ->setRules([
        'no_unused_imports' => true,
        'ordered_imports' => [
            'sort_algorithm' => 'alpha',
            'imports_order' => ['class', 'function', 'const'],
        ],
    ])
    ->setFinder($finder);

Usage

# Dry run — show what would change
vendor/bin/php-cs-fixer fix --dry-run --diff --rules='{"no_unused_imports": true}'

# Auto-fix unused imports
vendor/bin/php-cs-fixer fix --rules='{"no_unused_imports": true}'

# Using config file
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --dry-run --diff
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php

WordPress-Specific: Hook-Aware False Positive Filtering

WordPress plugins register callbacks dynamically. A function like this is NOT unused:

add_action('init', [$this, 'register_post_types']);
add_filter('the_content', 'my_plugin_filter_content');
add_action('wp_ajax_my_action', [$this, 'handle_ajax']);
add_action('wp_ajax_nopriv_my_action', [$this, 'handle_ajax']);
add_filter('plugin_action_links_' . MY_PLUGIN_BASENAME, [$this, 'add_settings_link']);

Hook Registration Patterns to Detect

When cross-referencing Psalm's unused code report against actual hook registrations, search for these patterns:

// Object method callbacks
add_action('hook_name', [$this, 'method_name']);
add_action('hook_name', [$instance, 'method_name']);
add_action('hook_name', [self::class, 'method_name']);
add_action('hook_name', [ClassName::class, 'method_name']);

// Function callbacks
add_action('hook_name', 'function_name');
add_action('hook_name', __NAMESPACE__ . '\\function_name');

// Closure callbacks (these don't produce false positives)
add_action('hook_name', function() { ... });

// Same patterns apply to add_filter()
add_filter('hook_name', [$this, 'method_name']);
add_filter('hook_name', 'function_name');

// Shortcode handlers
add_shortcode('my_shortcode', [$this, 'render_shortcode']);

// REST API route callbacks
register_rest_route('namespace/v1', '/route', [
    'callback' => [$this, 'handle_request'],
    'permission_callback' => [$this, 'check_permissions'],
]);

// Widget registration
register_widget(My_Widget::class);

// Block registration callbacks
register_block_type('namespace/block', [
    'render_callback' => [$this, 'render_block'],
]);

// Activation/deactivation hooks
register_activation_hook(__FILE__, [$this, 'activate']);
register_deactivation_hook(__FILE__, [$this, 'deactivate']);

// Cron callbacks
add_action('my_plugin_cron_event', [$this, 'run_scheduled_task']);

Cross-Reference Strategy

To filter false positives from Psalm output:

  1. Collect hook registrations — Grep the codebase for add_action, add_filter, add_shortcode, register_rest_route, register_block_type, register_widget, register_activation_hook, register_deactivation_hook.
  2. Extract callback names — Parse the second argument to get method/function names.
  3. Compare against Psalm findings — Any method/function Psalm flags as unused that appears in the hook registration list is a false positive.
  4. Report separately — Present "genuinely unused" vs "hook-registered (likely valid)" as distinct categories.

Grep Patterns for Hook Extraction

# Find all hook-registered callbacks (method references)
grep -rn "add_action\|add_filter\|add_shortcode\|register_rest_route\|register_block_type\|register_widget\|register_activation_hook\|register_deactivation_hook" \
  --include="*.php" src/ includes/ \
  | grep -oP "'\K[a-zA-Z_]+(?=')" | sort -u

# Find method-style callbacks: [$this, 'method_name'] or [self::class, 'method_name']
grep -rnoP "\[\s*(\\\$this|self::class|static::class|[A-Z][a-zA-Z_]*::class)\s*,\s*'([a-zA-Z_]+)'" \
  --include="*.php" src/ includes/

# Find function-style callbacks: 'function_name'
grep -rnoP "(add_action|add_filter)\s*\(\s*'[^']+'\s*,\s*'([a-zA-Z_\\\\]+)'" \
  --include="*.php" src/ includes/

Safe Removal Workflow

Follow this order to avoid cascading issues:

Step 1: Fix Missing Dependencies First

vendor/bin/composer-require-checker check
# Add any missing packages
composer require <missing-package>

Step 2: Remove Unused Dependencies

composer unused
# Review each, then remove
composer remove <unused-package>

Step 3: Clean Up Unused Imports

vendor/bin/php-cs-fixer fix --rules='{"no_unused_imports": true}'

Step 4: Remove Dead Code (Psalm)

vendor/bin/psalm --find-unused-code --output-format=json
# Cross-reference against hook registrations
# Remove genuinely unused code manually

Step 5: Verify Nothing Broke

composer install          # Ensure deps are consistent
vendor/bin/psalm          # Type check (without --find-unused-code)
vendor/bin/phpunit        # Run tests
# If WordPress: activate plugin in test environment and check for fatal errors
wp plugin activate <plugin-slug> --path=/path/to/wp

Interpreting Results

When presenting results:

  1. Group by severity — Missing dependencies are critical; unused imports are low-risk
  2. Separate hook callbacks — Always cross-reference Psalm's unused findings against hook registrations before recommending removal
  3. Check dynamic usage — WordPress plugins often use class_exists(), function_exists(), and string-based class instantiation
  4. Consider public API — Functions/classes intended for use by other plugins or themes may appear unused within the project itself
  5. Watch for conditional loading — WordPress plugins sometimes load files conditionally (admin-only, frontend-only) which can create apparent dead code

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.