Repository navigation
[architecture] Reassess isolated tooling distribution after CPX and domain extraction #197
Description
Proposal / validation gate: this issue records an extraction design for maintainer validation. Repository/package names are candidates. Approve the boundary and migration plan before implementation; this task does not authorize creating repositories, implementing the extraction, releases or consumer rollout.
Revised question — optional isolated distribution, not a required architecture (2026-10-21)
The old Box/PHP-Scoper + shared-runtime design remains below as historical rationale; it is not a prerequisite for #240 domain extraction. Open #376 proposes CPX external runtime execution and LLM Skills. Its candidate SHA cde0cc6 is not merged/released. Changelog is already independent; do not restart its extraction or assume domains must depend on a generic DevTools runtime/container.
Remaining design work
- Compare plain Composer library APIs, pinned external CPX CLI, packaged container and optional PHAR for each actual domain use case. Select the least complex surface; no mandated new dev-tools-shim repository.
- Distinguish resolver/process isolation from namespace/API isolation. PHPUnit test/extension APIs, ECS/Rector configs and consumer autoload can share a process even under CPX; establish fixtures for public/unscoped APIs, collisions and PHP minima.
- Keep domain -> DevTools production dependencies prohibited. DevTools may consume released domains or independent CLIs; build-time cycles and development feedback loops need explicit treatment, not a claim every require-dev edge is a solver cycle.
- If PHAR/scoping solves a demonstrated residual problem, define exactly which private dependencies are scoped, which public APIs remain normal packages, packaging/resource tests and version/update/rollback contract; compare cost to existing runtimes before proposing implementation.
- Avoid mandatory new dev-tools-runtime/composer-metadata helper repositories without independent reuse. Reuse available ecosystem primitives; DevTools retains only orchestration ([architecture] Make DevTools the profile-based bootstrap and upgrade orchestrator for domain packages #384), metadata policy candidate is [architecture] Extract repository-metadata policies without moving bootstrap orchestration #382.
- Record how feat!: distribute DevTools through CPX and portable skills #376 changes previous claims about Composer plugin ownership, BaseCommand adapters and skill/agent copying. No automatic closure of Normalize path-helper naming, namespace boundaries, and segment typing #163/Centralize repeated command process dispatch behind an explicit event-driven contract #164 cleanup or Define release-safe refs for consumer workflow wrappers and checked-out actions #238 ref policy.
The issue remains open with this narrower validation question. It does not ask to build a shim merely because the old proposal named one. Naming, artifact choice and implementation require maintainer validation.
Historical proposal — preserved, no longer prerequisite
Original #197 Box/PHP-Scoper proposal
Summary
Create a dedicated fast-forward/dev-tools-shim distribution built with Box + PHP-Scoper so Fast Forward tooling can be split into smaller reusable packages while still sharing the existing DevTools runtime, container, and automation capabilities.
Problem
fast-forward/dev-tools currently acts as one large Composer plugin and tool bundle. That works well for repository bootstrap, workflow sync, and the unified composer dev-tools entrypoint, but it makes it hard to extract parts of the project into smaller standalone packages that can be consumed independently.
A concrete example is changelog automation. A dedicated fast-forward/changelog package could be useful on its own in consumer repositories, but today the implementation is tightly coupled to the main dev-tools dependency graph and command surface.
At the same time, those smaller Fast Forward components would still benefit from DevTools capabilities during development, such as:
- the existing container and service-provider wiring
- command orchestration and process helpers
- changelog, docs, and workflow conventions
- shared QA tooling and CI surfaces
Goal
Introduce a dev-tools-shim artifact that packages the DevTools runtime in an isolated PHAR-style distribution, so that:
fast-forward/dev-toolscan evolve into an orchestrator and aggregator of smaller Fast Forward components- standalone Fast Forward component packages can depend on
fast-forward/dev-tools-shimas arequire-devtool dependency - those component packages can reuse DevTools internals and tooling without exposing DevTools' third-party dependency graph directly to their own consumers
Proposed Direction
- Create a dedicated
fast-forward/dev-tools-shimpackage built with Box + PHP-Scoper. - Scope the bundled runtime so the shim can safely embed DevTools internals and its third-party implementation dependencies.
- Keep
fast-forward/dev-toolsas the repository-facing orchestrator responsible for:- the Composer plugin integration
- workflow wrappers and local GitHub actions
- consumer sync flows
- packaged skills, packaged agents, and repository bootstrap defaults
- Allow extracted Fast Forward component packages to use
fast-forward/dev-tools-shimduring development and CI. - Move toward a model where
fast-forward/dev-toolsaggregates smaller Fast Forward components instead of permanently owning all behavior directly.
Why This Matters
This would let Fast Forward packages reuse a common runtime and toolchain while reducing direct dependency pressure on downstream consumers.
In practice, it could help with cases where the current dev-tools dependency set is too broad or too invasive, especially because the repository currently requires runtime and QA packages such as:
php-di/php-diphpunit/phpunitphpunit/php-code-coveragefriendsofphp/php-cs-fixerrector/rectorphpdocumentor/shimsymplify/easy-coding-standard- other tool-specific packages that are useful to DevTools itself but may be undesirable as direct dependency constraints for a smaller component package
A shim would also allow Fast Forward components to reuse the existing DI surface from DevToolsServiceProvider instead of duplicating container choices package by package.
Repository Constraints And Challenges To Capture
The issue SHOULD explicitly account for the current repository shape:
1. Composer-plugin and orchestrator split
fast-forward/dev-tools is currently a composer-plugin with:
- plugin capability registration
bin/dev-tools- synchronized workflow wrappers in
resources/github-actions/ - local reusable GitHub actions in
.github/actions/ - sync orchestration for repository defaults, skills, agents, changelog automation, wiki, and other managed assets
The shim cannot simply replace the main package. We need a deliberate split between:
- the repository orchestration package (
fast-forward/dev-tools) - the isolated reusable runtime/tool distribution (
fast-forward/dev-tools-shim)
2. Service container reuse
DevToolsServiceProvider currently wires a large shared runtime:
- process builder / queue
- filesystem and file locators
- changelog services
- git client
- console loader and logger services
- diffing, gitignore, gitattributes, license, Twig loader, clock, etc.
If extracted component packages are expected to reuse this runtime, we need a stable contract for what the shim exposes versus what remains private implementation detail.
3. Third-party dependency scoping strategy
A major motivation is avoiding consumer dependency conflicts, but some dependencies have public-facing development ergonomics.
Example challenge:
- a package that installs only the shim would not automatically have the real
phpunit/phpunitclasses available to extend in its own test suite unless we make an explicit decision about what is bundled, what is scoped, and what remains a normal Composer dependency.
We need a clear strategy for cases like:
- PHPUnit APIs used directly by package tests
- Prophecy / coverage helpers used directly in package tests
- tool configs or extension classes that are meant to be referenced by consumers
This may require selectively scoping only implementation dependencies, or splitting some concerns into separate packages instead of putting everything into one giant scoped PHAR.
4. Release and version propagation
If dev-tools-shim becomes a separate package or artifact, the release flow needs to keep it aligned with fast-forward/dev-tools.
One specific challenge is that fast-forward/dev-tools releases would need to trigger a shim release/update workflow so the shim can be rebuilt and published at the matching version.
The issue SHOULD cover:
- how the shim version mirrors DevTools releases
- whether the shim is released from the same repository or a dedicated repository
- what workflow handoff or cross-repository dispatch is required
5. Sync and workflow compatibility
fast-forward/dev-tools currently synchronizes workflow wrappers, local action references, packaged skills, packaged agents, and repository bootstrap files into consumers.
The shim should not blur those responsibilities. The issue SHOULD keep clear that the shim is for isolated runtime/tool reuse, while the main dev-tools package remains the owner of:
- consumer sync behavior
- workflow wrappers and repository automation defaults
- packaged agents / packaged skills surfaces
- broader repository orchestration
6. Artifact duplication and content divergence
Using a shim means some files will effectively exist twice:
- normal repository source in
fast-forward/dev-tools - scoped / packaged equivalents inside the shim artifact
That duplication is acceptable only if we define what is intentionally replicated and how drift is prevented when the packaged content differs from repository source because of scoping, stubs, or build-time rewriting.
Non-Goals
- This issue should not immediately extract all DevTools functionality into separate packages.
- This issue should not decide every final package boundary up front.
- This issue should not replace the existing
fast-forward/dev-toolsconsumer sync and workflow orchestration role.
Acceptance Criteria
- A design is documented for
fast-forward/dev-tools-shimusing Box + PHP-Scoper. - The design clearly distinguishes
fast-forward/dev-toolsorchestration responsibilities from shim responsibilities. - The plan covers how extracted component packages can depend on the shim in
require-devand reuse DevTools services safely. - The plan addresses public-vs-scoped dependency strategy, especially around PHPUnit and other developer-facing tool APIs.
- The plan addresses version propagation and release workflow coordination between DevTools and the shim artifact.
- The plan addresses how build-time replicated files are generated and kept in sync.
- Follow-up implementation issues can be split cleanly from this design issue.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsBacklog
Activity
coisa commentedon Apr 24, 2026
Discussion Draft: incremental component extraction and dev-tools-shim architecture
This is a proposed action plan for turning
fast-forward/dev-toolsinto an orchestrator of specialized Fast Forward packages while preserving the current user-facing ergonomics:composer ...commands;bin/dev-toolsworkflow for development;fast-forward/dev-tools-shimshould provide an isolated tool/runtime distribution for smaller Fast Forward packages.The important architectural distinction is that the current coupling is not just "we use Composer classes". The current package is doing several jobs at once:
The extraction should split these jobs gradually, not all at once.
Current Composer coupling map
Current direct Composer touchpoints found in the repository:
FastForward\DevTools\Console\DevTools extends Composer\Console\Applicationbin/dev-toolsbehave like a Composer-flavored console application even when the package is the root package and cannot consume itself as a Composer plugin.Symfony\Component\Console\Application. Keep Composer-specific behavior in a plugin bridge only.Composer\Command\BaseCommanddev-toolsexposes thin ComposerBaseCommandadapters only for the Composer plugin surface.Composer\Plugin\Capability\CommandProvider,Composer\Command\BaseCommandfilteringfast-forward/dev-toolsonly. It can create Composer adapters around shared handlers instead of returning the same standalone commands.PluginInterface,Capable,EventSubscriberInterface,ScriptEvents,Event,Composer,IOInterfacefast-forward/dev-tools. Usecomposer-plugin-apiinrequire; keepcomposer/composerinrequire-devfor tests/static analysis/autocomplete. Runtime classes come from the host Composer process.Composer\Factory::getComposerFile()ComposerJson,FundingCommand,UpdateComposerJsonCommandcomposer.jsonpath, including theCOMPOSERenv behavior.ProjectComposerFileResolverwith explicit tests forCOMPOSER, cwd, relative path, absolute path, and directory edge cases.Composer\Json\JsonFilecomposer.json,installed.json, and funding metadataJsonDocumentReader/JsonDocumentWriterusingjson_decode(..., JSON_THROW_ON_ERROR)plus repository-specific errors. Keep Composer's reader only behind an optional bridge if needed.Composer\Json\JsonManipulatorUpdateComposerJsonCommandto mutatecomposer.jsonwhile preserving structure.ComposerJsonEditorinterface. Start with currentJsonManipulatorbehind an implementation in a Composer bridge. Later evaluate a custom stable writer or a normalizer-driven approach.Composer\InstalledVersionsComposerJsonandRectorConfig.composer/composerby itself because it is generated in normal Composer installs.InstalledPackageRepositoryInterface. UseComposer\InstalledVersionswhen available; fallback tovendor/composer/installed.jsonwhen needed.Composer\Util\PlatformGithubActionOutputfor env checks.Environment/RuntimeEnvironmentabstraction aroundgetenv()/$_SERVER.IOInterfacethroughBaseCommand.IOInterfaceto the shared logger/output contract. Symfony adapters map Symfony IO/output to the same contract.getLoop()->getProcessExecutor()->execute(...)Target shape
The target model should be:
A key constraint: avoid a dependency cycle where component packages require-dev the shim while the shim also Composer-requires those same component packages. The safer approach is:
dev-tools;dev-toolsrequires components and wires them into consumer-facing orchestration;dev-tools-shimis built as an artifact/tool distribution and should avoid normal Composerrequireedges back into packages that are expected to require-dev it;Command architecture
For each command domain, use three layers:
Handler / application service
InputInterface, no ComposerBaseCommand, and no Composer IO.Symfony command adapter
bin/dev-toolsand future component-local binaries.Composer command adapter
fast-forward/dev-toolsonly;Composer\Command\BaseCommandif Composer capability compatibility requires it;Example for testing:
This keeps the package extractable while preserving the current
composer testsUX.Proposed package boundaries
1.
fast-forward/dev-tools-runtimePurpose:
dev-tools, components, and the shim;Likely contents:
Dependencies:
php:^8.3psr/logpsr/containerpsr/clocksymfony/consolesymfony/filesystemsymfony/findersymfony/processsebastian/diffcontainer-interop/service-providercomposer/composer2.
fast-forward/composer-metadataPurpose:
Likely contents:
ProjectComposerFileResolverComposerJsonReaderComposerJsonMetadataDTOs (Author,Support,Funding)InstalledPackageRepositoryInterfaceComposerJsonEditorInterfaceComposerJsonManipulatorEditorimplementation in a bridge namespace.Dependencies:
php:^8.3symfony/filesystemor runtime filesystem contractscomposer/composeronly in a dedicated bridge package orrequire-devMigration note:
Composer\Factory::getComposerFile()andComposer\Json\JsonFileshould be replaced early.JsonManipulatorshould be isolated behind an interface before deciding whether to replace it.3.
fast-forward/changelogPurpose:
Likely contents:
src/Changelog/*DependabotChangelogEntryMessageResolverentry,check,next-version,promote,showDependencies:
php:^8.3fast-forward/dev-tools-runtimeor smaller contracts packagepsr/logpsr/clockcomposer/composerdev-toolsrole:BaseCommandadapters forcomposer changelog:*;4.
fast-forward/testingPurpose:
Likely contents:
RunTestsHandlerTestsOptionsCoverageSummary,CoverageSummaryLoaderDependencies:
php:^8.3fast-forward/dev-tools-runtimepsr/logphpunit/php-code-coverageonly ifCoverageSummaryLoaderdirectly type-checksCodeCoveragephpunit/phpunita hard runtime dependency unless the package intentionally exposes PHPUnit extension APIs.Shim strategy:
require-devor a deliberately unscoped public testing extension package.Open design question:
fast-forward/phpunit-extensionwith real PHPUnit dependencies, while the shim owns isolated command execution.5.
fast-forward/documentationPurpose:
Likely contents:
Dependencies:
php:^8.3fast-forward/dev-tools-runtimetwig/twigfast-forward/phpdoc-bootstrap-templateShim strategy:
6.
fast-forward/code-qualityPurpose:
code-style,refactor,phpdoc, and maybestandardssubflows that primarily orchestrate external QA tools.Likely contents:
ecs.php,rector.php,.php-cs-fixer.dist.php;Dependencies:
php:^8.3fast-forward/dev-tools-runtimepsr/logrector/rectorsymplify/easy-coding-standardfriendsofphp/php-cs-fixerergebnis/rector-rulesrector/jackImportant caveat:
7.
fast-forward/dependency-healthPurpose:
Likely contents:
dependenciesbehavior;shipmonk/composer-dependency-analyserand Jack.Dependencies:
php:^8.3fast-forward/dev-tools-runtime8.
fast-forward/repository-syncPurpose:
Likely contents:
copy-resourcegitignoregitattributesgit-hookslicensefundingcodeownersagents/skillsuntil issue [architecture] Map domain-owned skills and replace redundant agent distribution #195 extracts agent payloads.Dependencies:
php:^8.3fast-forward/dev-tools-runtimefast-forward/composer-metadatasymfony/yamlcomposer/composerexcept optional metadata bridge.dev-toolsrole:dev-tools:syncas the orchestrator command that composes these handlers.9.
fast-forward/github-automationPurpose:
dev-toolsif we decide workflow sync is core orchestration.Likely contents if extracted:
resources/github-actions/*.github/actions/*source payloadsDependencies:
Recommendation:
dev-toolsorchestration and release coordination, so they can remain indev-toolsuntil smaller domain packages stabilize.10.
fast-forward/agentsPurpose:
Likely contents:
.agents/agents.agents/skillslater, if skills should share the same distribution strategy.Dependencies:
dev-toolsrole:What remains in
fast-forward/dev-toolsfast-forward/dev-toolsshould remain the repository-facing orchestrator:BaseCommandadapters;dev-tools:syncorchestration;Target dependencies:
php:^8.3composer-plugin-api:^2.0symfony/consolefast-forward/dev-tools-runtimefast-forward/changelog,fast-forward/testing, etc.composer/composershould move towardrequire-devonly, used for tests/static analysis/autocomplete around the plugin bridge.What
bin/dev-toolsshould becomeThe standalone binary should stop depending on
Composer\Console\Application.Target:
bin/dev-toolsboots a plainSymfony\Component\Console\Application;This keeps the current local DX while removing the main reason the whole console layer needs
composer/composer.What
dev-tools-shimshould dofast-forward/dev-tools-shimshould be treated as an isolated development tool distribution, not as the main repository orchestrator.Responsibilities:
Non-responsibilities:
Dependency scoping strategy
Use three buckets:
Public APIs that consumers/components may extend or reference
Implementation-only tool dependencies
Bridge dependencies
JsonManipulator, Composer plugin command adapters, host Composer runtime classes.A practical rule: if package users need to import a class in their own PHP code or config, that class should not only exist inside a scoped PHAR.
Migration phases
Phase 0: Architecture guardrails
BaseCommand.Composer\*imports outside approved bridge namespaces.Phase 1: Replace standalone Composer application
DevToolsfromComposer\Console\Applicationto SymfonyApplication.php bin/dev-tools listcomposer listcomposer agents --helpPhase 2: Introduce handler DTOs for one low-risk domain
Recommended first domain: changelog.
Why:
Deliverables:
ChangelogEntryHandler,ChangelogCheckHandler, etc.;Phase 3: Extract
fast-forward/changelogdev-tools.dev-toolsrequirefast-forward/changelog.dev-tools-shimin the changelog repo'srequire-devonce available.Phase 4: Extract runtime/core infrastructure
fast-forward/dev-tools-runtime.dev-tools.dev-toolspackage.Phase 5: Extract repository sync
dev-tools:syncindev-toolsas an orchestrator that calls those handlers.fast-forward/agentspayload extraction.Phase 6: Build the first
dev-tools-shimrequire-devand runs a command.Phase 7: Move heavy tool orchestration behind shim
Phase 8: Release propagation
dev-toolsreleases trigger or require a matchingdev-tools-shimrelease.fast-forward/dev-toolsin a consumer fixture;fast-forward/dev-tools-shiminrequire-dev;Suggested first follow-up issues
Composer\Console\Applicationwith Symfony Console forbin/dev-toolswhile keeping Composer plugin command discovery intact.ProjectComposerFileResolver,JsonDocumentReader, andComposerJsonEditorInterface.fast-forward/changelogas the first extracted component package.fast-forward/dev-tools-shimwith Box + PHP-Scoper using the smallest useful command set.Success criteria for the architecture
dev-toolscan still provide the unified Composer plugin UX.bin/dev-toolscan run without extending Composer's console application.dev-tools-shiminrequire-devwithout depending on the monolithicdev-toolspackage.[-][architecture] Design a Box + PHP-Scoper dev-tools-shim for extracted Fast Forward components[/-][+][architecture] Reassess isolated tooling distribution after CPX and domain extraction[/+]