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
Regression Criteria
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.
Problem
After the
#288sync changes, consumer repositories can still break when Git hooks fall back to the packagedgrumphp.ymlwhilefast-forward/dev-toolsis 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:This leaves globally synced repositories without a working managed GrumPHP fallback unless they provide their own local
./grumphp.yml.Current Behavior
ManagedConfigPathSynchronizeralready removes deprecated DevTools-managedextra.grumphp.config-default-pathentries, so the remaining failure is no longer coming fromcomposer.jsonmetadata.The active failure surface is the synchronized hook content:
HookContentRendererreplaces__DEV_TOOLS_GRUMPHP_CONFIG__withDevToolsPathResolver::getPackagePath('grumphp.yml')resources/git-hooks/pre-commitresources/git-hooks/commit-msgWhen a consumer repository does not define
./grumphp.yml, the rendered hook falls back to that absolute packaged path and invokes the localvendor/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:syncfrom 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:syncdev-tools git-hooksFastForward\DevTools\GitHooks\HookContentRendererresources/git-hooks/pre-commitresources/git-hooks/commit-msgfast-forward/dev-toolsProposal
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:
The important part is that global sync remains deterministic without forcing consumers to add their own
grumphp.ymljust to avoid the failure.Implementation Strategy
./grumphp.ymlfirst, DevTools-managed fallback second.Non-goals
extra.grumphp.config-default-pathsynchronization.#296.Acceptance Criteria
Functional Criteria
./grumphp.ymlcontinue to have that local file take precedence../grumphp.ymlcontinue to get a working DevTools-managed GrumPHP fallback after sync.Regression Criteria
grumphp.ymlfallback path../grumphp.ymloverride.Architectural / Isolation Criteria