GitHub Actions Workflows
FastForward DevTools provides a set of reusable GitHub Actions workflows that automate testing, documentation generation, and wiki synchronization. These workflows are synchronized into consumer repositories via the dev-tools:sync
command.
Workflow Layers
The automation model separates shared implementations from consumer triggers:
- Local composite and JavaScript actions
in
.github/actions/inside this repository. These contain the reusable implementation details for PHP setup, project-board transitions, GitHub Pages handling, wiki branch management, and related helpers. Changelog validation, version preparation and publication are implemented byphp-fast-forward/changelogand invoked through its external reusable workflow. - Reusable workflows
in
.github/workflows/inside this repository. These are the canonical automation entrypoints consumed by Fast Forward repositories and wrappers. - Consumer wrappers
copied from
resources/github-actions/*.ymlinto the consumer repository's.github/workflows/directory bydev-tools:sync. These wrappers keep triggers and repository-specific defaults local while delegating implementation to the reusable workflows inphp-fast-forward/dev-tools. - Workflow action source checkout
inside the reusable workflows when they
need local action implementations from
.github/actions/. The reusable workflow checks out the upstreamphp-fast-forward/dev-toolssource into a dedicated.dev-tools-actionsworkspace path. Jobs that use the shared PHP bootstrap can then prefer the consumer-localvendor/bin/dev-toolswhen it exists and otherwise install a deterministic fallback runtime from that workflow-source checkout.
Wrapper Workflows
Consumer repositories usually keep thin wrapper workflows in
.github/workflows/
that call the reusable workflows published by
php-fast-forward/dev-tools
.
Example of an inherited workflow:
name: "Fast Forward Reports"
uses: php-fast-forward/dev-tools/.github/workflows/reports.yml@main
secrets: inherit
This approach ensures that all libraries in the ecosystem benefit from infrastructure updates without requiring manual changes to every repository.
DevTools-owned wrappers intentionally follow the reviewed @main
workflow
contract so centrally deployed fixes reach consumers. This is an explicit
shared-infrastructure update policy, distinct from pinning third-party
actions. A workflow SHA alone would not freeze the existing action-source
checkout, which also follows DevTools main
; a fully immutable migration
would need to version both surfaces together. The external Changelog caller
instead selects its published @v0
channel; its update contract is documented
below.
The packaged wrappers currently include:
tests.ymlreports.ymlreview.ymlchangelog.ymlwiki.ymlfor read-only pull-request generation and direct publication frommainwiki-maintenance.ymlas manual-only migration guidance for legacy wrappersauto-assign.ymllabel-sync.yml
The optional resources/github-actions-optional/test-statuses.yml
template
is kept outside this synchronized directory. It requires an explicit copy and
repository-specific verification; dev-tools:sync
does not install it.
For the protected-branch-safe preview and publish model, see Branch Protection and Bot Commits.
The standard reusable workflows support workflow-only consumers without a
local fast-forward/dev-tools
Composer dependency. The shared
setup-composer
action prefers the
consumer vendor/bin/dev-tools
when it exists and otherwise exposes a
dev-tools
wrapper backed by the checked-out .dev-tools-actions
source.
For Pages configured to use GitHub Actions, the optional
reports-native.yml
workflow requires consumer-local DevTools and a template
version of at least 2.1.0. Follow the native Pages operator guide
before adopting it. The legacy reports.yml
workflow remains unchanged.
Dependabot Required Test Statuses
The optional standalone test-statuses.yml
workflow supplies commit-status
aliases for consumers whose branch protection requires unqualified names such
as Run Tests (8.3)
. The current first-party rollout targets twelve audited
consumer repositories with PHP 8.3, 8.4 and 8.5; this template does not infer an
arbitrary consumer's matrix or protection policy. Consumers protecting the
native qualified GitHub Actions checks do not need these aliases.
To opt in, copy
resources/github-actions-optional/test-statuses.yml
from a reviewed
DevTools checkout or installed package into the consumer repository as
.github/workflows/test-statuses.yml
. Review these contracts before merging
the copied file:
- The
workflow_run.workflowsname and the PHP metadata check must match the source workflow name, currentlyFast Forward Test Suite. - The source workflow path must match the API path check, currently
.github/workflows/tests.yml. - The caller job name must match all job-name checks. The template expects
tests / Run Tests (<version>)and control jobs prefixed withtests /. EXPECTED_PHP_VERSIONSmust list every expected version as JSON strings, currently["8.3","8.4","8.5"]. Align those versions and the resultingRun Tests (<version>)contexts with branch protection.
A different caller prefix or matrix needs a reviewed adjustment to the copied template. An additional observed test version, missing expected version, ambiguous job or invalid attempt makes final publication fail closed; the publisher must not silently mirror only a subset of the matrix.
After the file reaches the default branch, same-repository Dependabot push
runs trigger it through workflow_run
requested, in-progress and completed
events. Active attempts receive pending statuses, including reruns; terminal
attempts receive the actual per-version outcomes. A delayed start event reads
the current Run API state instead of overwriting completed results with pending.
The publisher does not check out source, install dependencies, retrieve
artifacts or caches, or run caller code. Its own job alone receives
actions: read
and statuses: write
. It verifies the source
repository, SHA, workflow ID/path/name, actor, event and attempt through
the Run API and rechecks that snapshot before writing. It validates the
configured matrix before publishing terminal results. Partial retries retain
completed results for unaffected versions and select the newest attempt for
rerun versions. A full rerun that fails or is cancelled before the matrix runs
must not reuse earlier successes: verified failed control jobs or a failed
source attempt produce failure statuses for blocked versions. Stale events and
superseded runs stop publication.
The target is the source push's verified head_sha
, never the
publisher's github.sha
, which points to the default branch.
Pull-request runs are deliberately excluded because they can test a
different merge commit; Dependabot's push run supplies this bridge.
Fork pull requests are outside this workflow's scope.
This bridge becomes active only after its file is merged into the default branch; merely adding the template to a pull request does not activate its lifecycle events. Verify a real Dependabot push and its required contexts after deployment. API reads and status writes are separate operations: scheduling, API availability and a state transition between requests prevent an atomic or instantaneous update of every context.
Ordinary Required Test Statuses
For ordinary opt-in runs, the reusable test workflow has a separate checkout-free pending publisher after PHP version resolution. It verifies the current run attempt and complete matrix, then marks all configured contexts pending before the test matrix can start. Opt-out and Dependabot skips still permit tests to run; Dependabot uses the optional lifecycle bridge described above when its protection policy needs aliases.
If pending publication fails, the matrix is blocked and the final publisher does not run. The final publisher requires successful pending publication and an actual success or failure matrix outcome; skipped or cancelled matrices cannot cause it to republish older successful jobs. Final results come from GitHub job metadata for that exact run, selecting the newest attempt for each version and requiring it to be completed. A failed-only retry can retain results from unaffected versions, while incomplete or ambiguous newest results stop publication.
Full reruns reexecute the ordered pending publisher. Rerunning only an individual successful job may retain its successful ancestors and therefore does not guarantee another pending publication. There is also a scheduling and PHP-resolution gap before the pending job runs. A failed API read before its first POST leaves existing statuses unchanged, and later API failures may leave only some contexts updated. Commit-status mirroring does not provide an atomic replacement of earlier results. Prefer native qualified checks when migrating a repository's protection policy. See Branch Protection and Bot Commits for the permission ceiling and bot-authored commit flow.
Fast Forward Reports
The reports.yml
workflow is responsible for generating technical documentation and quality reports.
Triggers: Push to ``main``. Pull Request (opened, synchronized, reopened, closed). * Manual trigger (workflow_dispatch).
Behavior:
**Main Branch**: Runs all checks and deploys the final reports to the root of the ``gh-pages`` branch.
Runs a post-deploy health check against the published reports index and coverage URLs with retry/backoff to account for Pages propagation.
Resolves the workflow PHP version from ``composer.lock`` or ``composer.json`` before installing dependencies.
Removes .dev-tools/cache
from the publish directory before deployment so repository-local tool caches never leak into GitHub Pages output.
Appends a run summary with the published docs, coverage, and metrics URLs plus deployment verification status.
Pull Requests
:
Generates a **Preview** of the documentation, coverage, and metrics.
Deploys the preview to gh-pages
under previews/pr-{number}/
.
Verifies the preview index and coverage URLs after deployment before posting preview links.
Posts a Sticky Comment
on the PR with links to the live preview, coverage report, and metrics site.
Appends a run summary with preview URLs and verification status.
Groups nested command output into collapsible GitHub Actions log sections so docs, tests, and metrics are easier to inspect independently.
**Cleanup**: When a PR is closed, the workflow automatically removes the preview directory from the ``gh-pages`` branch to keep the repository clean.
Concurrency
: New pushes to the same PR cancel older in-progress preview runs without affecting other PRs.
**Scheduled Cleanup**: A scheduled/manual cleanup removes stale ``previews/pr-{number}/`` directories for already closed pull requests.
Run Summary
: Closed-preview cleanup and orphan cleanup runs append a deterministic summary of the path or counts that were removed.
Fast Forward Wiki
The active wiki.yml
wrapper calls wiki-direct.yml
. The workflow follows
the container repository's direct-publication model: generate first with read
permissions, then publish the generated artifact in a separate job.
- Pull requests: generate API Markdown and upload an artifact without Wiki write credentials. There are no preview-branch or parent-pointer commits.
- Main publication:
only
mainpublishes. The publishing job downloads the artifact and updates a separate checkout of the GitHub Wiki; it does not check out or execute the project's source code. - Page ownership:
Home.mdis generated and replaced during publication. Generatedclasses/andfunctions/directories are replaced during publication so removed APIs do not leave stale pages. Maintain hand-written pages outside these generated directories and Home page. - Generation sources:
shared helpers and the Markdown renderer use the
workflow's selected source revision in a separate Composer installation.
Consumer dependencies are installed without plugins or scripts; their
manifest, lock file, version constraints and plugin allowlist are preserved.
The signed phpDocumentor binary and Markdown theme come from the isolated
renderer. DevTools generates its own API using the checked-out
bin/dev-tools; consumers use the explicit pinned renderer binary. - Local sync:
dev-tools:syncgenerates Markdown in.dev-tools/wiki-generatedand does not initialize a Git submodule. Explicitwiki --initremains available for legacy local workflows.
Migration from the pointer-based workflows requires updating the consumer's
wiki.yml
and removing its automatic maintenance triggers. The packaged
wiki-maintenance.yml
is now a manual-only compatibility stub. Normal sync
preserves existing files: replace the wrappers explicitly or review
composer dev-tools:sync --overwrite
. Sync does not delete files missing
from its packaged source directory.
Remove only the .github/wiki
gitlink and its matching .gitmodules
stanza when migrating; preserve other submodules and the separate GitHub Wiki
repository. Existing Wiki content is not deleted by removing the parent
pointer. Initialize the Wiki in GitHub before enabling publication.
Legacy reusable wiki-preview.yml
and wiki-maintenance.yml
implementations
remain available to consumers that have not migrated. Their branch and pointer
behavior is documented in Branch Protection and Bot Commits;
new wrappers use direct publication.
Fast Forward Tests
The tests.yml
workflow provides standard Continuous Integration.
- Runs PHPUnit tests across the supported PHP matrix.
- Resolves the minimum supported PHP minor version from
composer.lockorcomposer.jsonand builds the test matrix from that floor upward. - Runs dependency health as a separate required job.
- Defaults the dependency-health threshold to
--max-outdated=-1so outdated packages stay visible in CI without failing the workflow on count alone. - Surfaces logged command failures as native GitHub Actions error annotations, including file and line metadata when the command provides them.
- Writes a compact run summary with the resolved PHP-version source, test matrix, effective coverage threshold, and dependency-health threshold.
- Uses PR-scoped concurrency so newer pushes cancel older in-progress runs for the same pull request.
Fast Forward Changelog
The repository and packaged consumer callers delegate to
php-fast-forward/changelog/.github/workflows/changelog.yml@v0
.
The repository's changelog.yml
is an event-driven caller, with no
workflow_call
interface.
This explicitly selected channel tracks published 0.x releases, including
potentially breaking minor updates. Local CPX execution is fixed to 0.2.0;
resolve the channel and review its documented contracts before updating.
The external workflow owns fragment checks, feedback, Dependabot notes,
managed version PRs and optional publication.
The caller handles pull_request
and pull_request_target
lifecycle
events, pushes to main
and manual dispatch. Untrusted PR checks run without
write credentials; privileged jobs read their trusted base. The selected Linux
runner needs Docker, Bash and Git. It does not install the consumer Composer
dependencies or require the consumer to run PHP 8.5.
Both callers use the official README YAML with the owner-approved
name: Fast Forward Changelog
and publish: true
overrides and one changelog
job. They set base-branch: main
, source: tags
, comment-mode: always
,
prepare-release: true
and publish: true
. The job grants
contents: write
, pull-requests: write
, issues: read
and
statuses: write
; workflow-level permissions remain empty. The external
defaults select Ubuntu, GITHUB_TOKEN
, CHANGELOG.md
, .changelog
,
changelog/version
and the PR title chore: update changelog
.
Automatic PR-origin lookup uses the external default enabled policy. It resolves missing origins from committed fragment bytes and merged PR evidence without requiring fragment edits after opening a PR. On base pushes or manual dispatch, the workflow prepares or refreshes a single version PR and consumes only its accepted fragments. The single-job caller follows the external lifecycle directly, without local post-preparation jobs.
Merging the reviewed managed version PR automatically publishes its tag and GitHub Release from the exact approved commit. This configured operation is already authorized and requires no second per-release approval. The workflow never merges the version PR itself. Direct manual publication and changes to existing Releases require separate explicit authorization.
Require the exact commit status Fast Forward Changelog
on the current PR
head after a repository pilot confirms it. Managed-head validation uses the external
ownership protocol; a matching branch name alone is not a waiver. Maintainer
history exceptions must use the library's verified permission-based mechanism,
not an editable label alone. Preserve all independent repository test gates.
See the selected release's workflow contracts and External Fast Forward Changelog for local previews. Inspect generated PR ownership, notes, fragment deletions and exact-head statuses before approval.
Fast Forward Rigorous Review
The review.yml
workflow standardizes how Fast Forward repositories request
high-signal pull-request review when a branch is no longer a draft.
Triggers: Pull Request Target (ready_for_review). Manual trigger (workflow_dispatch).
Behavior: Resolves the target pull request from the lifecycle event or manual input. Inspects the changed file list and infers high-signal review surfaces such as workflows, local actions, packaged skills, packaged agents, docs, changelog, wiki output, and source or test changes. Writes a GitHub Actions step summary that points maintainers to the packaged ``review-guardian`` project agent and ``pull-request-review`` skill. Posts or updates a sticky pull-request comment with a deterministic review brief, sample changed files, and a ready-to-run prompt for the dedicated review agent. * Runs only when a pull request actually becomes ready for review, not on every draft update.
Inputs:
* pull-request-number
: required only for manual dispatch.
Repository permissions:
``contents: read``
pull-requests: write
This workflow does not replace human review. It standardizes the review intake moment and keeps the same review capability manually invokable for an already open pull request.
Maintenance Workflows
- Project Board Automation
:
Supports both direct repository use and ``workflow_call`` wrappers in
consumer repositories.
Uses
github.repository_ownerto determine the owning organization or user. For ``php-fast-forward`` repositories, defaults to the first organization Project V2 when no explicit project number is provided. Consumer repositories SHOULD either passprojectin the wrapper workflow or definePROJECTas a repository variable. The reusable workflows consistently resolve the board as ``inputs.project || vars.PROJECT || ''``. In practice that means: wrapper input wins, then the repository variable, and finally the php-fast-forward organization default when the reusable workflow supports it. Adds new issues to the project and moves them intoBacklog. Adds linked pull requests to the same project, mirrors milestone plus inferable project metadata, and keeps draft PRs in ``In progress`` until they are explicitly ready for review. Promotes approved pull requests intoReady to Merge. Moves merged pull requests and linked issues into ``Merged``. Uses the built-in workflow token for project updates. - Label Sync : Synchronizes repository labels with ecosystem standards.
Transient Failure Retry
This repository also keeps a local retry-transient-failures.yml
workflow
that watches completed workflow runs and decides whether a failed run looks
like a transient GitHub-side infrastructure problem rather than a logic bug in
the workflow itself.
Behavior: Runs only after one of the repository's core workflows finishes with a failure. Inspects failed job logs for transient GitHub-side signatures such as checkout or fetch HTTP 500 failures, Git transport RPC errors, and related internal-server-error patterns. Requests a rerun of failed jobs only when every failed job matches those transient signatures. Stops after one rerun attempt, so repeated failures still surface clearly to maintainers. * Appends a deterministic summary describing whether a rerun was requested or skipped.
Non-goals: It does not retry PHPUnit failures, lint failures, changelog validation, or other logic or quality-signal regressions. It does not introduce unbounded rerun loops.