Files
branch-usage-checker/docs/plans/2026-02-23-phpcs-captainhook-plan.md
Ismo Vuorinen 2a37912685 chore: cleanup unused deps and dead code (#43)
* chore: remove unused code and dead files

Remove InspireCommand, GitHubApiBranch DTO, PackagistApiStatsPayload DTO,
and GitHubRestApi fetcher. Remove InspireCommand reference from config.

* chore(deps): remove unused composer dependencies

Remove guzzlehttp/guzzle, laravel-zero/phar-updater, and
nunomaduro/termwind. Clean up dead autoload entries and orphaned
allow-plugins configuration.

* feat: improve CheckCommand input handling and error reporting

Add vendor/package slash format support, input validation, HTTP error
handling, and concurrent stats fetching. Replace exit() calls with
return statements. Add 13 new test cases with Http::fake coverage.

* build: rebuild PHAR executable

* chore: add CLAUDE.md project instructions

* docs: add design for PHPCS + CaptainHook integration

* docs: add implementation plan for PHPCS + CaptainHook

* build(deps): add squizlabs/php_codesniffer

* build: add composer lint and format scripts

* build(deps): add captainhook and hook-installer

* build: configure CaptainHook pre-commit hook for PHPCS

* docs: add lint/format commands and hook info to CLAUDE.md

* build: add markdownlint-cli2 configuration

* build: add lint:md, lint:ec, lint:all, and format:md composer scripts

* build: add editorconfig and markdownlint to pre-commit hook

* docs: add new lint and format commands to CLAUDE.md

* chore: add missing gitignore patterns

* refactor: replace Http facade with injected HttpFactory

- Resolve HttpFactory via the container instead of using the Http facade
  directly, improving testability and explicitness
- Simplify early-return logic in package metadata error handling
- Reformat long strings and closures for PSR-12 line length compliance
- Extract repeated stats URL prefix into variable in tests

* build: add Claude Code shared settings and skills

Add shared hooks (auto-format PHP on edit, block vendor/lock edits),
shared permissions for common dev commands, and two user-invocable
skills (/build-phar, /release-check).

* refactor: extract test constants to reduce duplication in CheckCommandTest

Replace 6 occurrences of the command string and 4 occurrences of the
stats URL with TEST_COMMAND and TEST_STATS_URL constants. Fixes
SonarCloud S1192 code smells and brings duplicated lines density
below the 3% threshold.

* refactor: extract resolveInput and fetchPackageMetadata from handle()

Reduce handle() from 8 return statements to 3 by extracting input
validation into resolveInput() and HTTP fetching into
fetchPackageMetadata(). Fixes SonarCloud S1142 (too many returns).

* fix: improve CheckCommand error handling, input normalization, and timeouts

- Return exit code 1 from catch block instead of silently succeeding
- Normalize vendor/package inputs to lowercase before validation
- Add 10-second HTTP timeouts to metadata fetch and pool requests
- Return false from outputSuggestions when no suggestions found

* test: stub HTTP in live-request tests and assert exception output

- Add Http::fake() to slash-format and two-argument tests to prevent
  real Packagist requests during CI
- Assert exception message in try-block test and expect exit code 1
  to match the updated catch behavior

* docs: fix command signature, remove stale Fetchers reference in CLAUDE.md

- Change {package} to {package?} and note vendor/package combined form
- Remove non-existent app/Fetchers/ directory reference
- Update HTTP dependency note to reflect HttpFactory injection

* fix: guard array_combine against mismatched Packagist stats

Add a length check before array_combine() so malformed stats
(different label/value counts) produce a warning and skip the
branch instead of throwing a ValueError.

* refactor: change outputSuggestions return type to void

No caller uses the return value. Replace bool return type with
void and remove the now-unnecessary return statements.

* fix: narrow Throwable catch to Exception, add docblocks, remove unused default

- Catch \Exception instead of \Throwable so programming errors (TypeError,
  OutOfMemoryError) propagate instead of being silently displayed
- Add minimal PHPDoc summaries to all CheckCommand methods for coverage
- Remove unused default `= []` on outputSuggestions parameter
- Update test to expect TypeError propagation via ->throws()

* refactor: extract constants, fix dead store, deduplicate test setup

- Add TIMEOUT_SECONDS and PACKAGIST_URL constants in CheckCommand
- Replace inline timeout(10) and URL strings with constant references
- Fix dead store: $deletable[$k] = $values['Total'] → $deletable[] = $k
- Eliminate unused $keys intermediate variable in outputSuggestions()
- Standardize string formatting to use sprintf() consistently
- Add TEST_METADATA_URL constant and fakePackageResponses() helper in tests
- Refactor 6 tests to use shared helper, reducing Http::fake duplication

* docs: fix capitalization and remove stale GitHub reference in CLAUDE.md

* docs: add design for fixing SonarCloud duplication quality gate

* docs: add implementation plan for SonarCloud duplication fix

* refactor(tests): parameterize input-validation tests to fix duplication gate

* feat: add PHPMD linter and fix Codacy markdownlint config

Add PHPMD as a dev dependency with cleancode, codesize, unusedcode, and
naming rulesets. Integrate via composer lint:phpmd script, lint:all, and
CaptainHook pre-commit hook.

Split markdownlint config into .markdownlint.jsonc (rules) and
.markdownlint-cli2.jsonc (ignores only) so Codacy's plain markdownlint
can discover the shared rules file.

* fix: rename markdownlint config to .json and disable MD043

Rename .markdownlint.jsonc to .markdownlint.json so Codacy discovers
it at highest priority, and add required-headings: false to explicitly
disable MD043 which flags docs with varying heading structures.

* fix: guard against ConnectionException in pool responses and catch Throwable

Pool responses can be Throwable (e.g. ConnectionException) instead of
Response objects, causing crashes on ->failed(). Add instanceof guard,
widen catch to Throwable with TypeError re-throw, and extract loop into
collectBranchStats() to stay under cyclomatic complexity threshold.
2026-02-23 09:32:16 +02:00

6.5 KiB

PHPCS + CaptainHook Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Add PHPCS/PHPCBF as a dev dependency with composer scripts and CaptainHook-managed pre-commit hook for automatic formatting.

Architecture: Install three dev packages (squizlabs/php_codesniffer, captainhook/captainhook, captainhook/hook-installer). Add composer lint and composer format scripts. Configure CaptainHook to run PHPCBF on staged PHP files before each commit. The existing phpcs.xml is unchanged.

Tech Stack: PHP 8.4, Composer, PHPCS/PHPCBF, CaptainHook


Task 1: Install squizlabs/php_codesniffer

Files:

  • Modify: composer.json (auto-updated by composer)
  • Modify: composer.lock (auto-updated by composer)

Step 1: Install the package

Run:

composer require --dev squizlabs/php_codesniffer

Expected: Package installs successfully. composer.json now lists squizlabs/php_codesniffer in require-dev.

Step 2: Verify phpcs works with existing config

Run:

vendor/bin/phpcs --standard=phpcs.xml app/ tests/

Expected: Either clean output or a list of violations. The command should not error out — it should find and use phpcs.xml.

Step 3: Verify phpcbf works

Run:

vendor/bin/phpcbf --standard=phpcs.xml app/ tests/

Note: phpcbf exits non-zero when it finds fixable issues. The || true prevents that from stopping execution. What matters is it runs without crashing.

Step 4: Commit

git add composer.json composer.lock
git commit -m "build(deps): add squizlabs/php_codesniffer"

Task 2: Add composer lint and format scripts

Files:

  • Modify: composer.json — add two entries to "scripts"

Step 1: Add the scripts

In composer.json, add these two entries inside the "scripts" object:

"lint": "vendor/bin/phpcs",
"format": "vendor/bin/phpcbf || true"

Note: phpcbf returns exit code 1 when it fixes files (which is normal behavior, not an error). The || true prevents composer from treating successful fixes as failures. If there are unfixable errors, phpcbf returns exit code 2, but || true masks that too — this is acceptable since composer lint is the proper check command.

Step 2: Verify composer lint works

Run:

composer lint

Expected: Runs phpcs against the project. Either reports violations or shows no output (clean).

Step 3: Verify composer format works

Run:

composer format

Expected: Runs phpcbf. Auto-fixes any fixable violations.

Step 4: Run tests to confirm nothing broke

Run:

composer test

Expected: All 14 tests pass.

Step 5: Commit

git add composer.json
git commit -m "build: add composer lint and format scripts"

Task 3: Install CaptainHook

Files:

  • Modify: composer.json (auto-updated by composer)
  • Modify: composer.lock (auto-updated by composer)

Step 1: Install captainhook and the hook-installer plugin

Run:

composer require --dev captainhook/captainhook captainhook/hook-installer

Note: The installer may prompt about allowing the plugin. Answer yes. If composer.json's config.allow-plugins needs updating, composer will do it automatically when you approve.

Step 2: Verify CaptainHook is available

Run:

vendor/bin/captainhook --version

Expected: Prints a version string (e.g. CaptainHook x.x.x).

Step 3: Commit

git add composer.json composer.lock
git commit -m "build(deps): add captainhook and hook-installer"

Task 4: Configure CaptainHook pre-commit hook

Files:

  • Create: captainhook.json

Step 1: Create the CaptainHook config

Create captainhook.json in the project root with this content:

{
  "pre-commit": {
    "enabled": true,
    "actions": [
      {
        "action": "vendor/bin/phpcbf --standard=phpcs.xml {$STAGED_FILES|of-type:php}",
        "config": {
          "label": "Fix code style with PHPCBF"
        }
      },
      {
        "action": "vendor/bin/phpcs --standard=phpcs.xml {$STAGED_FILES|of-type:php}",
        "config": {
          "label": "Check code style with PHPCS"
        }
      }
    ]
  },
  "pre-push": {
    "enabled": false,
    "actions": []
  },
  "commit-msg": {
    "enabled": false,
    "actions": []
  }
}

The two pre-commit actions run in order:

  1. phpcbf auto-fixes what it can
  2. phpcs checks for remaining violations — if any exist, the commit is blocked

Step 2: Install the hooks into .git/hooks

Run:

vendor/bin/captainhook install --force

Expected: CaptainHook installs hook scripts into .git/hooks/. Output mentions installing hooks.

Step 3: Verify the hook is installed

Run:

head -5 .git/hooks/pre-commit

Expected: Shows a CaptainHook-generated script (not a sample hook).

Step 4: Commit

git add captainhook.json
git commit -m "build: configure CaptainHook pre-commit hook for PHPCS"

Note: This commit itself will trigger the pre-commit hook for the first time. If it blocks due to formatting issues in existing files, run composer format first, then re-stage and commit.


Task 5: Fix existing violations and final verify

Step 1: Run the formatter on the whole project

Run:

composer format

Expected: Fixes any existing violations across app/ and tests/.

Step 2: Run the linter to confirm clean

Run:

composer lint

Expected: No violations reported (clean exit).

Step 3: Run tests to confirm nothing broke

Run:

composer test

Expected: All 14 tests pass.

Step 4: Commit any formatting changes

git add -A
git status
git commit -m "style: auto-fix code style with phpcbf"

Note: Only commit if there are actual changes. If composer format made no changes, skip this step.


Task 6: Update CLAUDE.md

Files:

  • Modify: CLAUDE.md — add composer lint and composer format to Commands section, note CaptainHook in Code Standards

Step 1: Update CLAUDE.md

In the ## Commands section, add:

- `composer lint` — Check code style (PHPCS)
- `composer format` — Auto-fix code style (PHPCBF)

In the ## Code Standards section, add a note:

- CaptainHook pre-commit hook runs PHPCBF then
  PHPCS on staged PHP files automatically

Step 2: Commit

git add CLAUDE.md
git commit -m "docs: add lint/format commands and hook info to CLAUDE.md"