Skip to content

Fix global GrumPHP hook fallback using an invalid packaged config path #305

@coisa

Description

@coisa

Problem

After the #288 sync changes, consumer repositories can still break when Git hooks fall back to the packaged grumphp.yml while fast-forward/dev-tools is installed globally.

In that flow the synchronized hooks pass an absolute packaged config path from the global DevTools installation into the consumer's local vendor/bin/grumphp.phar. That can fail with:

The absolute path "/Users/mentordosnerds/.composer/vendor/fast-forward/dev-tools/grumphp.yml" cannot be made relative to the relative path "". You should provide an absolute base path instead.

This leaves globally synced repositories without a working managed GrumPHP fallback unless they provide their own local ./grumphp.yml.

Current Behavior

ManagedConfigPathSynchronizer already removes deprecated DevTools-managed extra.grumphp.config-default-path entries, so the remaining failure is no longer coming from composer.json metadata.

The active failure surface is the synchronized hook content:

  • HookContentRenderer replaces __DEV_TOOLS_GRUMPHP_CONFIG__ with DevToolsPathResolver::getPackagePath('grumphp.yml')
  • resources/git-hooks/pre-commit
  • resources/git-hooks/commit-msg

When a consumer repository does not define ./grumphp.yml, the rendered hook falls back to that absolute packaged path and invokes the local vendor/bin/grumphp.phar --config <absolute-global-path> ....

The reported error text comes from Symfony\Component\Filesystem\Path::makeRelative(), which is consistent with GrumPHP or one of its dependencies trying to relativize the absolute packaged config path against an empty or relative base.

Expected Behavior

Running dev-tools:sync from a globally installed DevTools runtime MUST still produce working managed GrumPHP hooks.

If a consumer repository does not ship its own ./grumphp.yml, the DevTools-managed fallback MUST remain valid for local hook execution and MUST NOT depend on a path shape that GrumPHP later rejects.

Failure Surface

  • dev-tools:sync
  • dev-tools git-hooks
  • FastForward\DevTools\GitHooks\HookContentRenderer
  • resources/git-hooks/pre-commit
  • resources/git-hooks/commit-msg
  • consumer repositories synced from a globally installed fast-forward/dev-tools

Proposal

Adjust the managed GrumPHP hook fallback so global DevTools installs do not render hooks that pass an incompatible absolute packaged config path into the consumer's local GrumPHP binary.

The fix can choose any runtime-safe strategy, for example:

  • render a project-safe config path that GrumPHP can consume reliably;
  • invoke GrumPHP through a DevTools-owned command boundary that resolves the packaged config internally;
  • or materialize a managed project-local config only when the global fallback would otherwise be invalid.

The important part is that global sync remains deterministic without forcing consumers to add their own grumphp.yml just to avoid the failure.

Implementation Strategy

  • Isolate the managed GrumPHP config-path resolution used by synchronized hooks.
  • Update hook rendering and/or hook templates so the fallback path remains valid in global-install scenarios.
  • Preserve the current preference order: consumer ./grumphp.yml first, DevTools-managed fallback second.
  • Add regression coverage for hook rendering or execution wiring when DevTools runs outside the consumer repository.
  • Update docs if the fallback behavior changes in a user-visible way.

Non-goals

  • Do not reintroduce deprecated extra.grumphp.config-default-path synchronization.
  • Do not redesign the GrumPHP ruleset or hook task list in this issue.
  • Do not cover the broader extraction work tracked in #296.

Acceptance Criteria

Functional Criteria

  • Global DevTools installs no longer generate Git hooks that fail with the reported absolute-path/relative-path error when the managed GrumPHP fallback is used.
  • Consumer repositories that define ./grumphp.yml continue to have that local file take precedence.
  • Consumer repositories without ./grumphp.yml continue to get a working DevTools-managed GrumPHP fallback after sync.
  • Local non-global DevTools installs remain compatible.

Regression Criteria

  • Tests cover synchronized hook rendering or execution wiring for global DevTools installs.
  • Tests cover the no-local-grumphp.yml fallback path.
  • Tests cover preservation of the consumer-local ./grumphp.yml override.

Architectural / Isolation Criteria

  • MUST: The core logic MUST be isolated into dedicated classes or services instead of living inside command or controller entrypoints.
  • MUST: Responsibilities MUST be separated across input resolution, domain logic, processing or transformation, and output rendering when the change is non-trivial.
  • MUST: The command or controller layer MUST act only as an orchestrator.
  • MUST: The implementation MUST avoid tight coupling between core behavior and CLI or framework-specific I/O.
  • MUST: The design MUST allow future extraction or reuse with minimal changes.
  • MUST: The solution MUST remain extensible without requiring major refactoring for adjacent use cases.
  • MUST: Argument and option resolution MUST be validated separately from command execution logic.
  • MUST: Console formatting and rendering MUST stay separate from domain processing.
  • MUST: Exit behavior, error messaging, and generated output MUST remain deterministic and testable.
  • MUST: Data gathering or transformation MUST be isolated from filesystem writes or publishing steps.
  • MUST: Generated output ordering and formatting MUST remain deterministic across runs.
  • MUST: Re-running the workflow MUST be idempotent or clearly bounded in its side effects.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    Status

    Merged

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions