From 46c0957242bf7ca24096626ac5b149e2456b7859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Sun, 18 Oct 2026 04:33:25 -0300 Subject: [PATCH 1/3] chore: adopt Fast Forward Changelog and reconcile history --- .changelog/adopt-fast-forward-changelog.md | 6 + .github/workflows/changelog.yml | 39 +++++++ CHANGELOG.md | 121 ++++++++++----------- docs/maintenance/changelog-adoption.md | 69 ++++++++++++ scripts/plan-release-reconciliation.py | 72 ++++++++++++ 5 files changed, 245 insertions(+), 62 deletions(-) create mode 100644 .changelog/adopt-fast-forward-changelog.md create mode 100644 .github/workflows/changelog.yml create mode 100644 docs/maintenance/changelog-adoption.md create mode 100644 scripts/plan-release-reconciliation.py diff --git a/.changelog/adopt-fast-forward-changelog.md b/.changelog/adopt-fast-forward-changelog.md new file mode 100644 index 0000000..e32ec94 --- /dev/null +++ b/.changelog/adopt-fast-forward-changelog.md @@ -0,0 +1,6 @@ +--- +category: changed +type: patch +--- + +Adopt independent Fast Forward Changelog fragments and reviewed version pull requests, and reconcile historical publication dates and delivery pull-request references. diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 0000000..849b47d --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,39 @@ +name: Changelog + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] + pull_request_target: + branches: [main] + types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled, closed] + push: + branches: [main] + workflow_dispatch: + +permissions: {} + +jobs: + changelog: + permissions: + contents: write + pull-requests: write + issues: read + statuses: write + # Published 0.x channel, selected by the maintainer; reviewed at v0.2.0. + uses: php-fast-forward/changelog/.github/workflows/changelog.yml@v0 + with: + base-branch: main + runs-on: '["ubuntu-latest"]' + fragment-directory: .changelog + changelog-file: CHANGELOG.md + locale: en + tag-prefix: v + source: tags + resolve-pull-requests: true + managed-branch: changelog/version + version-pr-title: 'chore(release): prepare v{next_version}' + automation-actor: 'github-actions[bot]' + comment-mode: always + prepare-release: true + publish: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 70d24df..ae4d0fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,159 +5,156 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] - -## [1.7.0] - 2026-10-16 +## [1.7.0] - 2026-10-17 ### Added -- Add a portable `fast-forward-config` consumer skill in resources/skills with version-aware guidance, checked application examples, manual project/global installation and optional llm/skills Composer discovery. +- Add a portable `fast-forward-config` consumer skill in resources/skills with version-aware guidance, checked application examples, manual project/global installation and optional llm/skills Composer discovery. ([#34](https://github.com/php-fast-forward/config/pull/34)) ### Changed -- Give each CachedConfig wrapper an isolated, portable PSR-16 default key whose entries expire after 300 seconds. Cross-instance or cross-request reuse now requires an explicit cacheKey; old class-name entries are not reused. +- Give each CachedConfig wrapper an isolated, portable PSR-16 default key whose entries expire after 300 seconds. Cross-instance or cross-request reuse now requires an explicit cacheKey; old class-name entries are not reused. ([#31](https://github.com/php-fast-forward/config/pull/31)) ### Removed -- Remove the deprecated misspelled LamiasConfigAggregatorConfig class without an autoload alias fallback. This is an intentional compatibility break: replace imports, type declarations, and instantiations with LaminasConfigAggregatorConfig. +- Remove the deprecated misspelled LamiasConfigAggregatorConfig class without an autoload alias fallback. This is an intentional compatibility break: replace imports, type declarations, and instantiations with LaminasConfigAggregatorConfig. ([#32](https://github.com/php-fast-forward/config/pull/32)) ### Fixed -- Normalize each provider or PHP file's dot and slash paths before Laminas merges sources, preserving later-source precedence, list order, merge instructions, lazy loading, and file-cache behavior. -- Load, lock, and atomically replace the same resolved PhpFileConfig target, avoiding competing include_path files and anchoring relative inputs on first use across working-directory or Windows drive changes. -- Follow current file and directory symlinks, including external retargeting, literal POSIX targets containing URI syntax, and dangling chains or ancestor directories, preserving intermediate links and rejecting cyclic or unresolvable chains before replacement. -- Preserve existing POSIX file groups and permission bits during atomic persistence, rejecting replacements with InvalidArgumentException when required metadata cannot be retained or its functions are disabled. -- Report unavailable PHP functions required for file-path resolution as InvalidArgumentException before changing persistent configuration. -- Preserve native stream-protocol selection and trailing directory separators, preventing URI-like relative paths or malformed file locators from redirecting reads and persistent mutations. -- Retain missing-file read defaults for empty locators or paths containing NUL bytes and reject their persistent mutations before creating directories, locks, or files. -- Load PHP configuration at every recursive directory depth in deterministic depth-and-path order, preserve filename-pattern overrides, and include resolved files once without traversing child directory symlinks. -- Keep accessible configuration available when a child directory is unreadable or disappears during recursive discovery, while preserving root read failures and unrelated errors. -- Treat directory path metacharacters literally during top-level and recursive discovery, preventing similarly named sibling directories from contributing configuration. -- Include discovered configuration through resolved absolute paths so relative roots cannot load outside namesakes through include_path or changes to the working directory. -- Resolve valid source data on cache misses, evictions, non-array entries, and declined initial writes without a has()/get() race or mandatory cache readback, while preserving lazy cache hits. -- Report failed persistent cache mutations with RuntimeException when the backend returns false, retaining the local mutation for application recovery. -- Resolve PSR-11 container identifiers consistently, require the documented config. prefix, and report empty or unknown configuration identifiers as container not-found errors. -- Use core string functions for directory paths and container prefixes so runtime-only PHP 8.3 consumers do not require undeclared multibyte functions or PHP 8.4 polyfills. -- Document and test the explicit Laminas config_cache_enabled flag, writable parent requirements, and existing-file reuse for provider and directory file-cache examples. +- Normalize each provider or PHP file's dot and slash paths before Laminas merges sources, preserving later-source precedence, list order, merge instructions, lazy loading, and file-cache behavior. ([#35](https://github.com/php-fast-forward/config/pull/35)) +- Load, lock, and atomically replace the same resolved PhpFileConfig target, avoiding competing include_path files and anchoring relative inputs on first use across working-directory or Windows drive changes. ([#33](https://github.com/php-fast-forward/config/pull/33)) +- Follow current file and directory symlinks, including external retargeting, literal POSIX targets containing URI syntax, and dangling chains or ancestor directories, preserving intermediate links and rejecting cyclic or unresolvable chains before replacement. ([#33](https://github.com/php-fast-forward/config/pull/33)) +- Preserve existing POSIX file groups and permission bits during atomic persistence, rejecting replacements with InvalidArgumentException when required metadata cannot be retained or its functions are disabled. ([#33](https://github.com/php-fast-forward/config/pull/33)) +- Report unavailable PHP functions required for file-path resolution as InvalidArgumentException before changing persistent configuration. ([#33](https://github.com/php-fast-forward/config/pull/33)) +- Preserve native stream-protocol selection and trailing directory separators, preventing URI-like relative paths or malformed file locators from redirecting reads and persistent mutations. ([#33](https://github.com/php-fast-forward/config/pull/33)) +- Retain missing-file read defaults for empty locators or paths containing NUL bytes and reject their persistent mutations before creating directories, locks, or files. ([#33](https://github.com/php-fast-forward/config/pull/33)) +- Load PHP configuration at every recursive directory depth in deterministic depth-and-path order, preserve filename-pattern overrides, and include resolved files once without traversing child directory symlinks. ([#37](https://github.com/php-fast-forward/config/pull/37)) +- Keep accessible configuration available when a child directory is unreadable or disappears during recursive discovery, while preserving root read failures and unrelated errors. ([#37](https://github.com/php-fast-forward/config/pull/37)) +- Treat directory path metacharacters literally during top-level and recursive discovery, preventing similarly named sibling directories from contributing configuration. ([#37](https://github.com/php-fast-forward/config/pull/37)) +- Include discovered configuration through resolved absolute paths so relative roots cannot load outside namesakes through include_path or changes to the working directory. ([#37](https://github.com/php-fast-forward/config/pull/37)) +- Resolve valid source data on cache misses, evictions, non-array entries, and declined initial writes without a has()/get() race or mandatory cache readback, while preserving lazy cache hits. ([#31](https://github.com/php-fast-forward/config/pull/31)) +- Report failed persistent cache mutations with RuntimeException when the backend returns false, retaining the local mutation for application recovery. ([#31](https://github.com/php-fast-forward/config/pull/31)) +- Resolve PSR-11 container identifiers consistently, require the documented config. prefix, and report empty or unknown configuration identifiers as container not-found errors. ([#30](https://github.com/php-fast-forward/config/pull/30)) +- Use core string functions for directory paths and container prefixes so runtime-only PHP 8.3 consumers do not require undeclared multibyte functions or PHP 8.4 polyfills. ([#30](https://github.com/php-fast-forward/config/pull/30)) +- Document and test the explicit Laminas config_cache_enabled flag, writable parent requirements, and existing-file reuse for provider and directory file-cache examples. ([#36](https://github.com/php-fast-forward/config/pull/36)) ## [1.6.0] - 2026-10-16 ### Changed -- Replace dflydev/dot-access-data with native array storage, path traversal, normalization, and merging while preserving configuration lookup, assignment, and aggregation behavior. Empty lookup paths now raise FastForward\Config\Exception\InvalidArgumentException instead of the removed dependency's exception. +- Replace dflydev/dot-access-data with native array storage, path traversal, normalization, and merging while preserving configuration lookup, assignment, and aggregation behavior. Empty lookup paths now raise FastForward\Config\Exception\InvalidArgumentException instead of the removed dependency's exception. ([#17](https://github.com/php-fast-forward/config/pull/17)) ### Fixed -- Restore Wiki API documentation generation and main-branch publication, preserve Wiki history and unrelated pages, normalize internal page links, and verify published content. +- Restore Wiki API documentation generation and main-branch publication, preserve Wiki history and unrelated pages, normalize internal page links, and verify published content. ([#13](https://github.com/php-fast-forward/config/pull/13)) ## [1.5.1] - 2026-10-09 ### Added -- Add a contextual repository mascot banner to the README. +- Add a contextual repository mascot banner to the README. ([#4](https://github.com/php-fast-forward/config/pull/4)) ### Fixed -- Restore Reports generation and publish documentation, coverage, and metrics through native GitHub Pages artifact deployment, with site verification after deployment and job-scoped permissions. -- Concatenate sequential lists when aggregating configuration sources, preserving order and duplicates, recursive map merging, later scalar precedence, dot notation, and lazy loading without changing explicit ArrayConfig::set() index-replacement semantics. -- Mirror verified Dependabot push test outcomes through a separate completion workflow so required statuses are available with the bot's restricted token. -- Allow the isolated CI status publisher to read workflow job outcomes without granting write permissions to test jobs. +- Restore Reports generation and publish documentation, coverage, and metrics through native GitHub Pages artifact deployment, with site verification after deployment and job-scoped permissions. ([#9](https://github.com/php-fast-forward/config/pull/9), [#10](https://github.com/php-fast-forward/config/pull/10), [#11](https://github.com/php-fast-forward/config/pull/11)) +- Concatenate sequential lists when aggregating configuration sources, preserving order and duplicates, recursive map merging, later scalar precedence, dot notation, and lazy loading without changing explicit ArrayConfig::set() index-replacement semantics. ([#7](https://github.com/php-fast-forward/config/pull/7)) +- Mirror verified Dependabot push test outcomes through a separate completion workflow so required statuses are available with the bot's restricted token. ([#5](https://github.com/php-fast-forward/config/pull/5)) +- Allow the isolated CI status publisher to read workflow job outcomes without granting write permissions to test jobs. ([#5](https://github.com/php-fast-forward/config/pull/5)) ## [1.5.0] - 2026-09-17 ### Added -- Add PhpFileConfig with optional persistence and configFile() helper -- Add comprehensive Sphinx documentation under docs/ +- Add PhpFileConfig with optional persistence and configFile() helper. ([#2](https://github.com/php-fast-forward/config/pull/2)) +- Add comprehensive Sphinx documentation under docs/. ### Changed -- Raise minimum PHP version requirement to ^8.3 -- Implement atomic file persistence using brick/varexporter and temporary files in PhpFileConfig -- Restrict automatic PHP file detection in config() helper to files ending with .php +- Raise minimum PHP version requirement to ^8.3. +- Implement atomic file persistence using brick/varexporter and temporary files in PhpFileConfig. ([#2](https://github.com/php-fast-forward/config/pull/2)) +- Restrict automatic PHP file detection in config() helper to files ending with .php. ([#2](https://github.com/php-fast-forward/config/pull/2)) ### Deprecated -- Deprecate LamiasConfigAggregatorConfig in favor of LaminasConfigAggregatorConfig +- Deprecate LamiasConfigAggregatorConfig in favor of LaminasConfigAggregatorConfig. ([#3](https://github.com/php-fast-forward/config/pull/3)) ### Fixed -- Allow setting falsy values (false, 0, '0', '', null) with string keys in ArrayConfig::set() -- Synchronize multi-instance persistent file mutations via sidecar process locking and fresh file reloads in PhpFileConfig -- Correct Sphinx section underline markup for LamiasConfigAggregatorConfig in docs/api-interfaces.rst -- Preserve restrictive file permissions when atomically persisting configuration -- Preserve symlink targets and update real file target during persistent writes -- Verify complete byte count on temporary file write to prevent partial file corruption -- Rename typo LamiasConfigAggregatorConfig to LaminasConfigAggregatorConfig while retaining LamiasConfigAggregatorConfig as a deprecated compatibility alias +- Allow setting falsy values (false, 0, '0', '', null) with string keys in ArrayConfig::set(). ([#2](https://github.com/php-fast-forward/config/pull/2)) +- Synchronize multi-instance persistent file mutations via sidecar process locking and fresh file reloads in PhpFileConfig. ([#2](https://github.com/php-fast-forward/config/pull/2)) +- Correct Sphinx section underline markup for LamiasConfigAggregatorConfig in docs/api-interfaces.rst. ([#3](https://github.com/php-fast-forward/config/pull/3)) +- Preserve restrictive file permissions when atomically persisting configuration. ([#2](https://github.com/php-fast-forward/config/pull/2)) +- Preserve symlink targets and update real file target during persistent writes. ([#2](https://github.com/php-fast-forward/config/pull/2)) +- Verify complete byte count on temporary file write to prevent partial file corruption. ([#2](https://github.com/php-fast-forward/config/pull/2)) +- Rename typo LamiasConfigAggregatorConfig to LaminasConfigAggregatorConfig while retaining LamiasConfigAggregatorConfig as a deprecated compatibility alias. ([#3](https://github.com/php-fast-forward/config/pull/3)) ## [1.4.0] - 2025-06-11 ### Added -- Implement ArrayAccess in ConfigInterface via ArrayAccessConfigTrait -- Add remove() method to ConfigInterface for removing configuration keys +- Implement ArrayAccess in ConfigInterface via ArrayAccessConfigTrait. ([#1](https://github.com/php-fast-forward/config/pull/1)) +- Add remove() method to ConfigInterface for removing configuration keys. ([#1](https://github.com/php-fast-forward/config/pull/1)) ## [1.3.0] - 2025-05-24 ### Added -- Introduce ConfigHelper with isAssoc(), normalize(), and flatten() utilities -- Support directory path strings directly in config() helper +- Introduce ConfigHelper with isAssoc(), normalize(), and flatten() utilities. ### Changed -- Update getIterator() across config classes to yield flattened dot-notated key-value pairs +- Update getIterator() across config classes to yield flattened dot-notated key-value pairs. -## [1.2.0] - 2025-05-23 +## [1.2.0] - 2025-05-24 ### Added -- Support custom cache keys and persistent cache updates on set() in CachedConfig +- Support custom cache keys and persistent cache updates on set() in CachedConfig. ## [1.1.4] - 2025-04-20 ### Fixed -- Require 'config.' prefix for configuration key lookup in ConfigContainer +- Require 'config.' prefix for configuration key lookup in ConfigContainer. ## [1.1.3] - 2025-04-20 ### Fixed -- Return wrapped configuration instance when resolved by container alias in ConfigContainer -- Correct package name in installation instructions in README.md +- Return wrapped configuration instance when resolved by container alias in ConfigContainer. +- Correct package name in installation instructions in README.md. ## [1.1.2] - 2025-04-19 ### Fixed -- Rename ContainerNotFoundExceptionInterface to ContainerNotFoundException +- Rename ContainerNotFoundExceptionInterface to ContainerNotFoundException. ## [1.1.1] - 2025-04-18 ### Fixed -- Preserve sequential arrays and non-string keys during array normalization in ArrayConfig +- Preserve sequential arrays and non-string keys during array normalization in ArrayConfig. ## [1.1.0] - 2025-04-18 ### Added -- Add PSR-11 container implementation via ConfigContainer +- Add PSR-11 container implementation via ConfigContainer. ## [1.0.0] - 2025-04-18 ### Added -- Initial release of FastForward Config library -- In-memory configuration storage with dot-notation support via ArrayConfig -- Directory and recursive directory configuration loaders via DirectoryConfig and RecursiveDirectoryConfig -- PSR-16 cache wrapper via CachedConfig -- Laminas ConfigAggregator bridge via LamiasConfigAggregatorConfig -- Helper functions config(), configCache(), configDir(), and configProvider() +- Initial release of FastForward Config library. +- In-memory configuration storage with dot-notation support via ArrayConfig. +- Directory and recursive directory configuration loaders via DirectoryConfig and RecursiveDirectoryConfig. +- PSR-16 cache wrapper via CachedConfig. +- Laminas ConfigAggregator bridge via LamiasConfigAggregatorConfig. +- Helper functions config(), configCache(), configDir(), and configProvider(). +- Support directory path strings directly in config() helper. -[unreleased]: https://github.com/php-fast-forward/config/compare/v1.7.0...HEAD [1.7.0]: https://github.com/php-fast-forward/config/compare/v1.6.0...v1.7.0 [1.6.0]: https://github.com/php-fast-forward/config/compare/v1.5.1...v1.6.0 [1.5.1]: https://github.com/php-fast-forward/config/compare/v1.5.0...v1.5.1 diff --git a/docs/maintenance/changelog-adoption.md b/docs/maintenance/changelog-adoption.md new file mode 100644 index 0000000..bf8b052 --- /dev/null +++ b/docs/maintenance/changelog-adoption.md @@ -0,0 +1,69 @@ +# Changelog adoption and Release reconciliation + +## Selected runtime and workflow contract + +Investigated on 2026-10-18. Latest stable release: [v0.2.0](https://github.com/php-fast-forward/changelog/releases/tag/v0.2.0), published 2026-10-18T07:15:48Z; tag and commit: `2d3f84fa9e5350f03636153fdb3ae2a822e5ae60`. +The maintainer explicitly selected the moving `@v0` workflow channel. The live `refs/heads/v0` resolved to that same SHA during validation. This is an intentional 0.x update policy, including potentially breaking minor updates, rather than an immutable pin. + +Read the release's README, consumer SKILL.md, caller, reusable lifecycle and check/version workflows, CLI/history/policy contracts. The CPX cache already contains exactly v0.2.0 at this source reference; its binary runs under PHP 8.5.11. CPX itself could not write its host metadata lock in the sandbox, so validation invokes this verified installed binary directly. No project dependency or global skill was installed. Local CLI commands must select version 0.2.0 explicitly rather than copy older ranges embedded in the upstream README. + +The consumer uses English documentation, `main`, `CHANGELOG.md`, `.changelog`, the `v` tag prefix, tag-based history and `resolve-pull-requests: true`. All planning, preparation, checks and any future publication must use these same settings. Ordinary fragments omit unknown PR metadata; the resolver can prove it after merge without a follow-up fragment edit. + +Existing workflows use GitHub-hosted Linux/Windows runners and default GitHub tokens. Repository Actions variable and secret inventories were empty; no existing changelog caller, managed version PR, Changesets directory or pending fragments were found. No unrelated workflow or Composer dependency was removed. The new lifecycle uses Linux with Docker/Bash/Git, the default GitHub Bot, managed branch `changelog/version`, and the established title `chore(release): prepare v{next_version}`. `publish: false` is deliberate. A default-token version PR may need workflow approval under GitHub policy; configure an App only through a separately approved credential setup. + +## Evidence and historical corrections + +Baseline: `038deddd474e87528528539d0668d060939ef973` (main and v1.7.0). Inspected the complete first-parent history and each successive tag interval, merged PR API records and the affected source/workflow diffs. All 13 tags resolve to commits reachable from main, and match their remote refs; 13 corresponding GitHub Releases exist. The first interval is repository root through v1.0.0; every later interval is the previous listed version through that tag. Annotated tags are peeled to commits in the table. + +- Preserve the introduction, all historical descriptions and all 13 correct version-link definition lines byte for byte. Remove only the empty legacy Unreleased heading and its HEAD link. No pending descriptions need migration. +- Use the UTC calendar date of GitHub `published_at` consistently. v1.2.0 was published at 2025-05-24T02:06:08Z (2025-05-23 locally); v1.7.0 at 2026-10-17T01:39:54Z (2026-10-16 locally). The old dates can be explained by local time, but were not the UTC publication dates. No timestamps are rewritten on GitHub. +- Move directory-path support in `config()` from 1.3.0 to 1.0.0: it is already in `v1.0.0:src/functions.php`; `v1.2.0..v1.3.0` does not change that file. Preserve its description without inventing a PR. +- PR #2 delivered PhpFileConfig, persistence safeguards and the falsy-value fix; #3 delivered the compatibility alias and Sphinx underline correction. The historical notes imported by #2 do not make it the delivery PR for older behavior. PHP 8.3/Sphinx baseline work was integrated directly, so those notes retain no PR. +- PR #1 delivered ArrayAccess/remove(); #4 the mascot; #5 both status fixes; #7 list aggregation. Reports deployment required #9, #10 and #11, all within v1.5.0..v1.5.1. The version-preparation role of #9 is not used to attribute unrelated changes. +- v1.6.0: #17 native storage; #13 Wiki publication. #14 removes an obsolete Wiki submodule pointer; retain the existing user-facing Wiki summary rather than add an implementation-only release bullet. +- v1.7.0: #34 skill; #31 cache changes; #32 alias removal; #35 provider normalization; #33 file persistence; #37 directory discovery; #30 container/portable functions; #36 file-cache documentation. #38 only prepared release notes and is not their delivery PR. +- Earlier direct commits prove the existing 1.0.0–1.3.0 descriptions. No corresponding delivery PR was proved; no hashes, authors or issues are inserted as substitute note references. No duplicate versions or conflicting identities were found. Old promises, including the 1.5.0 compatibility alias subsequently removed in 1.7.0, remain in their historical context. + +## Reconciliation inventory + +File status is the proposed canonical history. Every existing Release requires a body-only update; no creation, deletion, tag move, target change, asset change or draft/prerelease change is planned. All are non-draft, non-prerelease and have no assets at capture time. Dates below are publication evidence, not commit timestamps. + +| Tag | Peeled commit SHA | Version | Proven publication (UTC) | CHANGELOG | GitHub action | +| --- | --- | --- | --- | --- | --- | +| v1.7.0 | `038deddd474e87528528539d0668d060939ef973` | 1.7.0 | 2026-10-17T01:39:54Z | present; reviewed | PATCH body | +| v1.6.0 | `3282f877808a23382cd41f4ed30946fcc10ec515` | 1.6.0 | 2026-10-16T19:19:22Z | present; reviewed | PATCH body | +| v1.5.1 | `7a85fa548bb10f81550d4d9b576c337a765f73ca` | 1.5.1 | 2026-10-09T18:05:12Z | present; reviewed | PATCH body | +| v1.5.0 | `bee3ac57a9f7f0a377022d5723c71e7c9bea4242` | 1.5.0 | 2026-09-17T23:46:33Z | present; reviewed | PATCH body | +| v1.4.0 | `930434f7f7aaf02d551eb53ab86ed25e6511522c` | 1.4.0 | 2025-06-11T14:38:34Z | present; reviewed | PATCH body | +| v1.3.0 | `eccc7bb8d0cd566b4001462c8e6726b295f0da44` | 1.3.0 | 2025-05-24T16:41:44Z | present; reviewed | PATCH body | +| v1.2.0 | `89385178faece2292ee8f3fbeb0eb953f70a120f` | 1.2.0 | 2025-05-24T02:06:08Z | present; reviewed | PATCH body | +| v1.1.4 | `36ae2c0da0f895adc5b105f08b0871c4a841a7ec` | 1.1.4 | 2025-04-20T15:19:41Z | present; reviewed | PATCH body | +| v1.1.3 | `795abbee1b5bba6ef08e1d2ce9fdd7e32f5c68da` | 1.1.3 | 2025-04-20T03:38:06Z | present; reviewed | PATCH body | +| v1.1.2 | `53818d6da40ff061c603abedaf656834ca179b3e` | 1.1.2 | 2025-04-19T15:46:55Z | present; reviewed | PATCH body | +| v1.1.1 | `f8d530ece45dee2a3308b58a32fe9a46ae6baa14` | 1.1.1 | 2025-04-18T23:32:42Z | present; reviewed | PATCH body | +| v1.1.0 | `d54a476287f280b8c3f964a279299778acd089e2` | 1.1.0 | 2025-04-18T22:46:15Z | present; reviewed | PATCH body | +| v1.0.0 | `6a83377fdaff883b7825760cd9191b5f5084aa88` | 1.0.0 | 2025-04-18T19:10:42Z | present; reviewed | PATCH body | + +## Concrete Release differences and authorized follow-up + +The new body for each row is exactly the output of the selected runtime's `notes VERSION --source=tags --repository=php-fast-forward/config`. All body links are inline; no footer context is required. The version heading and document footer are excluded by the official extractor. New PR links are added to 1.4.0–1.7.0; older empty/single-line bodies receive the existing curated categories and descriptions. 1.3.0 loses the misattributed directory-support note, which moves to 1.0.0. 1.6.0 loses its extra **Full Changelog** line. No automatic authors, commit lists or second generated change list are included. + +Run the read-only planner from a clean, fetched checkout with the exact reviewed CLI: + +```sh +python3 scripts/plan-release-reconciliation.py --changelog-bin /absolute/path/to/verified/v0.2.0/changelog +``` + +It verifies every remote tag (including annotated tag peeling), ancestry, exact canonical HEAD and clean history, extracts the official bodies, and emits per-version unified diffs, original/desired SHA-256, Release ID/flags/assets and a minimal API request. It never writes to GitHub. This PR's concrete JSON plan is also retained with the task review artifacts; regenerate it after integration to bind it to the actual merged SHA. + +The library's publication validator is intended for approved version transactions and refuses divergent published notes. It is not used to overwrite historical Releases. After explicit reconciliation authorization AND integration of the canonical history, fetch and verify the merged SHA, reviews, unresolved threads and gates. Rerun this planner there, compare the fresh Release snapshots, and review each request. Execute only the approved `PATCH /repos/php-fast-forward/config/releases/{id}` with JSON `{"body": ""}`; do not send tag_name, target_commitish, name, flags or assets. If a missing Release is found later, stop and review that case separately, including that creation now does not establish historical publication. Finally GET each affected Release and compare body bytes plus protected metadata/assets. Preserve any concurrent changes and investigate instead of overwriting them. + +## Maintenance authority and validation + +This is a normal human migration PR with one independent patch fragment. Central-history edits use the library's legitimate `changelog-maintenance` policy: a live label grant by a GitHub User with verified maintain/admin permission, matching numeric ID/login and the exact PR HEAD. The investigating account is `coisa`, User ID `426835`; the collaborator API reports `permission: admin`, `role_name: admin`. Verify the actual label timeline grant and current permission again for the published head. No trailers, Bot signatures or managed-branch ownership are fabricated. The caller does not disable any check. On initial adoption, privileged feedback only becomes available from the approved base workflow after integration. + +No managed version PR exists to regenerate. After approved integration, let the automation generate its own version PR and independently verify Bot ownership, signed head, current base and consumed fragments. Do not manually populate or edit its branch. + +Validation results are recorded in the PR after the final SHA is published. Applicable consumer cases are manual history, correct reference footer, empty pending section, published versions, divergent Releases, known/unknown PR references and next-version generation. No-file/no-tag and Changesets/mixed cases do not describe this consumer; relevant upstream coverage at the selected SHA is in `tests/History/HistoryCodecTest.php`, `HistoryReferenceBuilderTest.php`, `Import/HistoryImporterTest.php`, `tests/Release/ReleaseHistoryConsolidatorTest.php`, `tests/Integration/AutomaticPullRequestReferenceTest.php`, `tests/Console/Command/NotesCommandTest.php` and `tests/Automation/Policy/PullRequestPolicyTest.php`. Those tests are evidence of upstream coverage, not a claim they ran in this consumer. + +No unresolved version/date identity remains after choosing UTC dates. Unproved historical PR identities deliberately remain absent. Merge, generated-version integration, publication enablement and all real Release writes still require explicit authorization. diff --git a/scripts/plan-release-reconciliation.py b/scripts/plan-release-reconciliation.py new file mode 100644 index 0000000..ae89cee --- /dev/null +++ b/scripts/plan-release-reconciliation.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Print a read-only GitHub Release body reconciliation plan using official notes.""" + +import argparse +import difflib +import hashlib +import json +import re +import subprocess + + +def run(*args): + return subprocess.check_output(args, text=True) + + +parser = argparse.ArgumentParser(description=__doc__) +parser.add_argument('--changelog-bin', required=True, help='Verified v0.2.0 CLI binary') +args = parser.parse_args() +repository = 'php-fast-forward/config' +head = run('git', 'rev-parse', 'HEAD').strip() +if run('git', 'status', '--porcelain', '--', 'CHANGELOG.md'): + raise SystemExit('Commit the canonical changelog before planning.') +releases = json.loads(run('gh', 'api', '--paginate', '--slurp', f'repos/{repository}/releases')) +releases = [release for page in releases for release in page] +by_tag = {release['tag_name']: release for release in releases} +tags = run('git', 'tag', '--list', 'v*', '--sort=-version:refname').splitlines() +if set(by_tag) - set(tags): + raise SystemExit('A Release has no local tag. Fetch and investigate before proceeding.') +plan = [] +for tag in tags: + if not re.fullmatch(r'v\d+\.\d+\.\d+', tag): + raise SystemExit(f'Investigate unsupported version identity: {tag}') + sha = run('git', 'rev-parse', f'{tag}^{{commit}}').strip() + remote_ref = json.loads(run('gh', 'api', f'repos/{repository}/git/ref/tags/{tag}'))['object'] + while remote_ref['type'] == 'tag': + remote_ref = json.loads(run('gh', 'api', f"repos/{repository}/git/tags/{remote_ref['sha']}"))['object'] + if remote_ref['type'] != 'commit' or remote_ref['sha'] != sha: + raise SystemExit(f'Local/remote tag conflict: {tag}') + subprocess.run(['git', 'merge-base', '--is-ancestor', sha, head], check=True) + notes = run(args.changelog_bin, 'notes', tag[1:], '--source=tags', + '--repository=' + repository, '--no-interaction') + # This consumer deliberately uses only inline links in release bodies. + # Fail rather than exporting unresolved reference-style links. + if re.search(r'(? Date: Sun, 18 Oct 2026 04:42:45 -0300 Subject: [PATCH 2/3] chore: keep release reconciliation tooling outside the repository --- docs/maintenance/changelog-adoption.md | 10 +--- scripts/plan-release-reconciliation.py | 72 -------------------------- 2 files changed, 2 insertions(+), 80 deletions(-) delete mode 100644 scripts/plan-release-reconciliation.py diff --git a/docs/maintenance/changelog-adoption.md b/docs/maintenance/changelog-adoption.md index bf8b052..bb90d81 100644 --- a/docs/maintenance/changelog-adoption.md +++ b/docs/maintenance/changelog-adoption.md @@ -48,15 +48,9 @@ File status is the proposed canonical history. Every existing Release requires a The new body for each row is exactly the output of the selected runtime's `notes VERSION --source=tags --repository=php-fast-forward/config`. All body links are inline; no footer context is required. The version heading and document footer are excluded by the official extractor. New PR links are added to 1.4.0–1.7.0; older empty/single-line bodies receive the existing curated categories and descriptions. 1.3.0 loses the misattributed directory-support note, which moves to 1.0.0. 1.6.0 loses its extra **Full Changelog** line. No automatic authors, commit lists or second generated change list are included. -Run the read-only planner from a clean, fetched checkout with the exact reviewed CLI: +The concrete JSON plan, unified diffs, original/desired SHA-256 values and Release metadata are retained with the task review artifacts, outside this repository. Recreate that evidence after integration from a clean, fetched checkout using the exact reviewed CLI's `notes` command and read-only GitHub API calls. Verify every remote tag (including annotated tag peeling), ancestry, canonical HEAD and Release metadata before reviewing the minimal body-only requests. -```sh -python3 scripts/plan-release-reconciliation.py --changelog-bin /absolute/path/to/verified/v0.2.0/changelog -``` - -It verifies every remote tag (including annotated tag peeling), ancestry, exact canonical HEAD and clean history, extracts the official bodies, and emits per-version unified diffs, original/desired SHA-256, Release ID/flags/assets and a minimal API request. It never writes to GitHub. This PR's concrete JSON plan is also retained with the task review artifacts; regenerate it after integration to bind it to the actual merged SHA. - -The library's publication validator is intended for approved version transactions and refuses divergent published notes. It is not used to overwrite historical Releases. After explicit reconciliation authorization AND integration of the canonical history, fetch and verify the merged SHA, reviews, unresolved threads and gates. Rerun this planner there, compare the fresh Release snapshots, and review each request. Execute only the approved `PATCH /repos/php-fast-forward/config/releases/{id}` with JSON `{"body": ""}`; do not send tag_name, target_commitish, name, flags or assets. If a missing Release is found later, stop and review that case separately, including that creation now does not establish historical publication. Finally GET each affected Release and compare body bytes plus protected metadata/assets. Preserve any concurrent changes and investigate instead of overwriting them. +The library's publication validator is intended for approved version transactions and refuses divergent published notes. It is not used to overwrite historical Releases. After explicit reconciliation authorization AND integration of the canonical history, fetch and verify the merged SHA, reviews, unresolved threads and gates. Extract the notes there, compare the fresh Release snapshots, and review each request. Execute only the approved `PATCH /repos/php-fast-forward/config/releases/{id}` with JSON `{"body": ""}`; do not send tag_name, target_commitish, name, flags or assets. If a missing Release is found later, stop and review that case separately, including that creation now does not establish historical publication. Finally GET each affected Release and compare body bytes plus protected metadata/assets. Preserve any concurrent changes and investigate instead of overwriting them. ## Maintenance authority and validation diff --git a/scripts/plan-release-reconciliation.py b/scripts/plan-release-reconciliation.py deleted file mode 100644 index ae89cee..0000000 --- a/scripts/plan-release-reconciliation.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -"""Print a read-only GitHub Release body reconciliation plan using official notes.""" - -import argparse -import difflib -import hashlib -import json -import re -import subprocess - - -def run(*args): - return subprocess.check_output(args, text=True) - - -parser = argparse.ArgumentParser(description=__doc__) -parser.add_argument('--changelog-bin', required=True, help='Verified v0.2.0 CLI binary') -args = parser.parse_args() -repository = 'php-fast-forward/config' -head = run('git', 'rev-parse', 'HEAD').strip() -if run('git', 'status', '--porcelain', '--', 'CHANGELOG.md'): - raise SystemExit('Commit the canonical changelog before planning.') -releases = json.loads(run('gh', 'api', '--paginate', '--slurp', f'repos/{repository}/releases')) -releases = [release for page in releases for release in page] -by_tag = {release['tag_name']: release for release in releases} -tags = run('git', 'tag', '--list', 'v*', '--sort=-version:refname').splitlines() -if set(by_tag) - set(tags): - raise SystemExit('A Release has no local tag. Fetch and investigate before proceeding.') -plan = [] -for tag in tags: - if not re.fullmatch(r'v\d+\.\d+\.\d+', tag): - raise SystemExit(f'Investigate unsupported version identity: {tag}') - sha = run('git', 'rev-parse', f'{tag}^{{commit}}').strip() - remote_ref = json.loads(run('gh', 'api', f'repos/{repository}/git/ref/tags/{tag}'))['object'] - while remote_ref['type'] == 'tag': - remote_ref = json.loads(run('gh', 'api', f"repos/{repository}/git/tags/{remote_ref['sha']}"))['object'] - if remote_ref['type'] != 'commit' or remote_ref['sha'] != sha: - raise SystemExit(f'Local/remote tag conflict: {tag}') - subprocess.run(['git', 'merge-base', '--is-ancestor', sha, head], check=True) - notes = run(args.changelog_bin, 'notes', tag[1:], '--source=tags', - '--repository=' + repository, '--no-interaction') - # This consumer deliberately uses only inline links in release bodies. - # Fail rather than exporting unresolved reference-style links. - if re.search(r'(? Date: Sun, 18 Oct 2026 04:44:34 -0300 Subject: [PATCH 3/3] chore: install bundled changelog skill and remove migration report --- .../.fast-forward-install.json | 21 +++ .agents/skills/fast-forward-changelog/LICENSE | 21 +++ .../skills/fast-forward-changelog/SKILL.md | 142 ++++++++++++++++++ .../examples/contribution.php | 45 ++++++ .../references/automation.md | 58 +++++++ .../fast-forward-changelog/references/cli.md | 77 ++++++++++ .../references/php-api.md | 40 +++++ .changelog/adopt-fast-forward-changelog.md | 2 +- .gitattributes | 1 + .gitignore | 5 +- docs/maintenance/changelog-adoption.md | 63 -------- 11 files changed, 410 insertions(+), 65 deletions(-) create mode 100644 .agents/skills/fast-forward-changelog/.fast-forward-install.json create mode 100644 .agents/skills/fast-forward-changelog/LICENSE create mode 100644 .agents/skills/fast-forward-changelog/SKILL.md create mode 100644 .agents/skills/fast-forward-changelog/examples/contribution.php create mode 100644 .agents/skills/fast-forward-changelog/references/automation.md create mode 100644 .agents/skills/fast-forward-changelog/references/cli.md create mode 100644 .agents/skills/fast-forward-changelog/references/php-api.md delete mode 100644 docs/maintenance/changelog-adoption.md diff --git a/.agents/skills/fast-forward-changelog/.fast-forward-install.json b/.agents/skills/fast-forward-changelog/.fast-forward-install.json new file mode 100644 index 0000000..1dc46c0 --- /dev/null +++ b/.agents/skills/fast-forward-changelog/.fast-forward-install.json @@ -0,0 +1,21 @@ +{ + "schema": 1, + "owner": "fast-forward/changelog", + "name": "fast-forward-changelog", + "scope": "project", + "target": ".agents/skills/fast-forward-changelog", + "source": { + "package": "fast-forward/changelog", + "version": "v0.2.0", + "reference": "2d3f84fa9e5350f03636153fdb3ae2a822e5ae60" + }, + "files": { + "LICENSE": "58bd7c00603e499c575d53a97b8b8c1d1398f584ea5f9f77d369780efb2066ab", + "SKILL.md": "bef12db180f4c8812019ca9286ebaba722fca9850c6d6cfa6a9e7f0e060b4ac2", + "examples/contribution.php": "46f2b30277b4f880a8fc4f5d503d2a4fd13c6802a929159b1895f55f9a97dd2e", + "references/automation.md": "1096976a165c2a0f849470312996b4cdffaf43a18ca04be6e0f823720b47eccb", + "references/cli.md": "787f33efcbc72d40ce5b55507f6f0a59b686bffe4d7edfcf4242c427d7db023e", + "references/php-api.md": "32b67728f3c4ba5870a40f5ad88946dd274f84619676d4783642780a172cade8" + }, + "payload_sha256": "6aa35eb349f80bad8fc78a2cad7995b2fe39e41948f9538ad8a7b0b662ab9fa8" +} diff --git a/.agents/skills/fast-forward-changelog/LICENSE b/.agents/skills/fast-forward-changelog/LICENSE new file mode 100644 index 0000000..f320900 --- /dev/null +++ b/.agents/skills/fast-forward-changelog/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Felipe Sayao Lobato Abreu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.agents/skills/fast-forward-changelog/SKILL.md b/.agents/skills/fast-forward-changelog/SKILL.md new file mode 100644 index 0000000..69fb4dd --- /dev/null +++ b/.agents/skills/fast-forward-changelog/SKILL.md @@ -0,0 +1,142 @@ +--- +name: fast-forward-changelog +description: Adopt Fast Forward Changelog's GitHub lifecycle, write and check independent fragments, review version PRs, and perform authorized release maintenance. Discover the selected release's documentation and CLI capabilities before choosing integration details. +license: MIT +compatibility: Requires access to the application repository and the selected release's documentation. Verify that release's runner or PHP requirements before execution. Local CLI examples use CPX; PHP integration uses the application's Composer installation. GitHub credentials are needed only for authorized remote operations. +metadata: + author: php-fast-forward + source: original + repository: https://github.com/php-fast-forward/changelog + api-baseline: Verify the selected published release; commands and integration details are release-specific. +--- + +# Fast Forward Changelog + +Ordinary contributions add independent `.changelog/*.md` fragments. GitHub +automation checks each contribution, maintains PR feedback and prepares a +reviewable version PR. That PR consolidates the accepted fragments into +`CHANGELOG.md` and deletes only the consumed files. Publication follows a +separate approval for the exact result; a green check or prepared PR is not that +approval. + +## Select the real contract + +1. Read application instructions, Git status and its existing changelog setup. + Use its reviewed version for an ordinary task; do not upgrade it incidentally. + If its ref is a moving channel, resolve the current commit and corresponding + published full tag before selecting a local runtime. `v0` is not Composer `0`. +2. For new adoption or a requested update, discover the latest published stable + release in the [official repository](https://github.com/php-fast-forward/changelog/releases). + Confirm the complete release tag and its commit, exclude drafts/prereleases, + and respect any explicitly selected maintenance line. For CPX or PHP use, + confirm the same version is available on + [Packagist](https://packagist.org/packages/fast-forward/changelog). +3. Read that tag's README and relevant documentation, not `main` or an old copy + of this skill. Before adding a workflow, inspect its + `resources/workflows/changelog.yml`, companion README and the reusable workflow + they reference. Copy the release's caller, review inputs/permissions and set + its product references to that verified full tag or immutable release commit; + do not retain an obsolete embedded pin blindly. After setting the final ref, + inspect the reusable workflow at that exact ref and confirm its inputs, + permissions and runner requirements before saving the application caller. + Keep publication + disabled for adoption. Change existing integration only within task scope. +4. For local CLI work, set `CHANGELOG_VERSION` to the verified exact numeric + release, without the tag's `v`. Discover commands and inspect their arguments: + + ```sh + cpx --skip-local "fast-forward/changelog:$CHANGELOG_VERSION" list --raw + cpx --skip-local "fast-forward/changelog:$CHANGELOG_VERSION" help add + cpx --skip-local "fast-forward/changelog:$CHANGELOG_VERSION" help check + ``` + + Use [CPX's official setup](https://github.com/laravel/cpx) when installation is + authorized; do not pin the executor in these commands. `--skip-local` selects + its separate tool installation. CPX may handle `--help` or `--version` itself, + so use `help ` for changelog help. An existing reviewed project binary + may be used directly instead. Confirm prerequisites from the chosen release. + +Installing this skill installs instructions, independently of the runtime. If +the selected CLI offers skill installation/check/update operations, inspect +their help and use only the supported operation and requested scope. Otherwise +follow that release's skill-installation guide; do not invent a command. + +This procedure avoids a frozen API inventory. A breaking release can still +change the lifecycle or these entrypoints: when documentation and capabilities +disagree, resolve the mismatch before writes rather than guessing compatibility. + +## Write and check a contribution + +Read the change and describe its observable effect. For example, create one +unique `.changelog/preserve-markdown-spaces.md`: + +```markdown +--- +category: fixed +type: patch +--- + +Preserves meaningful Markdown spaces when parsing fragments. +``` + +Keep existing notes and Markdown intact. Use the selected release's fragment +schema. For the established category scheme, `added`, `changed` and `deprecated` +default to `minor`, `removed` to `major`, and `fixed`/`security` to `patch`. +Persist the effective type explicitly; a justified override may express a +compatible dependency change as `changed`/`patch`. Apply the application's +versioning policy deliberately, especially during `0.x` development. + +After help confirms these arguments, the CLI can create and validate that note +from the application directory instead of writing the example manually: + +```sh +cpx --skip-local "fast-forward/changelog:$CHANGELOG_VERSION" add "Preserves meaningful Markdown spaces when parsing fragments." --category=fixed --no-interaction +cpx --skip-local "fast-forward/changelog:$CHANGELOG_VERSION" check --no-interaction +``` + +Verify that exactly one intended fragment was created and central history stayed +unchanged. Do not run both authoring routes for the same note. Fix diagnostics +and rerun the check. Before a committed PR comparison, inspect `help check`, +fetch the intended baseline under existing authority, then use its supported +comparison option, for example: + +```sh +cpx --skip-local "fast-forward/changelog:$CHANGELOG_VERSION" check --since=origin/main --no-interaction +``` + +That comparison requires committed contribution changes. A local uncommitted +fragment can pass local validation without satisfying the PR comparison. + +Optional PR/issue/author metadata must come from verified evidence. When the +origin PR is already known and supported, use `--pull-request=` or +`pull_request: ` after confirming its repository and exact head. Leave +unknown fields absent; do not invent a number or require a second fragment +commit after opening a PR. If the selected release resolves PR origins during +compilation, follow its documented policy consistently across preparation and +publication. No qualifying PR should become an invented commit or author link. + +Ordinary work preserves inherited fragments and published history. Do not add a +new `Unreleased` section. Editorial replacement, history maintenance and release +consolidation require their own task scope. + +## Verify the lifecycle + +For adoption, open a small pilot PR and inspect the exact head's required status, +sticky comment and independent application checks. Exercise a missing or +malformed note and any enabled bot route. Inspect the generated version PR: +planned version/impact, exact notes, central-history changes and only the accepted +fragment deletions. Review again after its head changes. The workflow does not +merge its own version PR. + +Load only the reference needed for the task: + +- [Automation](references/automation.md): selected caller, optional settings, + credentials, maintenance lines and pilot verification. +- [CLI maintenance](references/cli.md): previews, consolidation, history, + interrupted transactions and separately authorized publication. +- [PHP integration](references/php-api.md): release-specific application + services and the bundled example, only when programmatic integration is needed. + +Retain authority already given, but do not infer commit, push, merge, installation +elsewhere or publication authority from an ordinary fragment task. Report changed +paths, executed checks and the verified version/PR/SHA relevant to the result. diff --git a/.agents/skills/fast-forward-changelog/examples/contribution.php b/.agents/skills/fast-forward-changelog/examples/contribution.php new file mode 100644 index 0000000..1fdd8de --- /dev/null +++ b/.agents/skills/fast-forward-changelog/examples/contribution.php @@ -0,0 +1,45 @@ + + * @license https://opensource.org/licenses/MIT MIT License + */ + +use FastForward\Changelog\Container\ServiceProvider\ChangelogServiceProvider; +use FastForward\Changelog\Fragment\FragmentWriterInterface; +use FastForward\Changelog\Release\Factory\ReleaseOptionsFactoryInterface; +use FastForward\Changelog\Validation\CheckServiceInterface; + +use function FastForward\Container\container; + +$autoload = $argv[1] ?? ''; +$directory = realpath($argv[2] ?? ''); +if ('' === $autoload || !is_file($autoload) || !isset($argv[2]) || false === $directory || !is_dir($directory)) { + fwrite(STDERR, "Usage: php contribution.php /consumer/vendor/autoload.php /existing/project\n"); + exit(2); +} +require $autoload; + +$container = container(ChangelogServiceProvider::class); +$options = $container->get(ReleaseOptionsFactoryInterface::class)->create([ + 'workingDirectory' => $directory, + 'source' => 'tags', +]); +$path = $container->get(FragmentWriterInterface::class)->add( + $options, + 'Preserves meaningful Markdown spaces when parsing fragments.', + category: 'fixed', + name: 'php-api-example.md', +); +$expected = "---\ncategory: fixed\ntype: patch\n---\n\nPreserves meaningful Markdown spaces when parsing fragments.\n"; +if ($expected !== file_get_contents($path)) { + throw new RuntimeException('The fragment does not match the expected application output.'); +} +if (!$container->get(CheckServiceInterface::class)->check($options)->isValid()) { + throw new RuntimeException('The pending fragment inventory is invalid.'); +} +fwrite(STDOUT, "Contribution example PASS\n"); diff --git a/.agents/skills/fast-forward-changelog/references/automation.md b/.agents/skills/fast-forward-changelog/references/automation.md new file mode 100644 index 0000000..46e36e1 --- /dev/null +++ b/.agents/skills/fast-forward-changelog/references/automation.md @@ -0,0 +1,58 @@ +# GitHub lifecycle adoption + +Load when installing or changing workflows, configuring credentials or runners, +selecting maintenance lines, or diagnosing feedback. Discover the latest stable +published release for new adoption, respecting an explicitly selected release +line. For ordinary work, retain the application's reviewed implementation. + +## Use the selected release's caller + +From the verified full tag, read `README.md`, +`resources/workflows/changelog.yml`, its companion README, and the reusable +workflow referenced by that caller. Consult that tag's relevant guides, including +`docs/reusable-lifecycle.md`, `docs/github-app.md` and `docs/automation-map.md` when +present. If paths moved, follow that release's README rather than guessing. +Do not combine a caller from one version with settings copied from `main`. + +Copy the caller into the application's `.github/workflows/`, review its events, +inputs and effective permissions, then set its product references to the selected +published complete tag or verified immutable release commit. Review older pins +embedded in the copied caller. After replacing a pin, inspect the reusable +workflow at the final selected ref and verify its actual inputs, permissions and +runner requirements before saving the application caller. +GitHub `uses` references the executable reusable +workflow under `.github/workflows`; the resource caller is a copyable asset. +Internal `repository-*` workflows belong to the library, not the consumer. + +Keep publication disabled during adoption. Choose base and managed branches, +fragment/history paths, historical sources and PR-reference policy consistently +across enabled operations. For parallel maintenance, select a separate managed +branch per release line and verify its actual planned version; a branch's name +does not enforce a SemVer range. Use individual workflows or Docker actions only +when their release-specific contracts suit the application's orchestration. + +Full tags preserve a concrete implementation. A moving major channel is an +explicit update policy and must be documented by the selected product release; +do not assume it exists. In particular `@v0` may cross breaking `0.x` minors and +does not mean Composer `^0.1`. Changing a workflow ref is a deliberate update. + +## Credentials and acceptance + +Use the selected workflow's documented token/App route and the application's +approved secret storage. Preserve the verified automation identity and narrow +permissions. Never check out untrusted PR code in a privileged custom job. +Token-generated events may behave differently from human/App events; consult +the selected release and GitHub's current rules before promising downstream CI. + +Run a small pilot with publication disabled. Verify a valid fragment, missing or +malformed notes, the exact PR head's required status and sticky-comment recovery, +any enabled bot operation, and the generated version PR. Check the actual runner +requirements and access when changing Linux/self-hosted selection. Test custom +policy changes relevant to the application rather than assuming a default-branch +green run proves its generated head. + +The version PR must preserve exact notes and consume only its accepted fragment +set, with no technical plan files or new `Unreleased` section. Head changes need +fresh review and checks. Successful preparation does not authorize merging or +publication. Enable publication only under existing authority for the verified +result, using the selected release's committed evidence and recovery procedure. diff --git a/.agents/skills/fast-forward-changelog/references/cli.md b/.agents/skills/fast-forward-changelog/references/cli.md new file mode 100644 index 0000000..55eea75 --- /dev/null +++ b/.agents/skills/fast-forward-changelog/references/cli.md @@ -0,0 +1,77 @@ +# CLI maintenance + +Load for release previews, history maintenance, recovery or authorized +publication. First select the real release and inspect `list --raw` and +`help ` as described in the skill. Read `docs/cli-fragments.md` and the +relevant maintenance/publication guide from that complete release tag. Verify +that its PHP and Git requirements match the environment. Do not infer arguments +or current behavior from an older installed skill. + +## Execute the selected CLI + +Run from the application's directory. `CHANGELOG_VERSION` is the verified exact +numeric product version, not a range frozen into this skill: + +```sh +cpx --skip-local "fast-forward/changelog:$CHANGELOG_VERSION" list --raw +cpx --skip-local "fast-forward/changelog:$CHANGELOG_VERSION" help status +cpx --skip-local "fast-forward/changelog:$CHANGELOG_VERSION" help version +``` + +CPX keeps tool dependencies separate from the application's Composer project; +the command still operates on application files. It is not a filesystem sandbox +or a PHP runtime. CPX's own update/help/version commands concern the executor. +An existing reviewed `vendor/bin/changelog` may run the same supported arguments +without changing the application's dependencies. + +When `list` offers instruction-management operations, inspect their help before +using install, check or update options. Use the requested project/global scope +and preserve local changes. Their existence is a discovered capability, not a +promise that every published changelog version contains them. Runtime updates, +instruction installation and host-session activation remain separate results. + +## Preview before consolidation + +For releases offering these arguments, inspect the read-only plan first: + +```sh +cpx --skip-local "fast-forward/changelog:$CHANGELOG_VERSION" status --json --source=tags --no-interaction +cpx --skip-local "fast-forward/changelog:$CHANGELOG_VERSION" version --dry-run --source=tags --no-interaction +``` + +Review the baseline/current/next version, effective impact, accepted fragment +paths, notes and affected files. Choose the relevant maintenance branch and +historical source explicitly. A branch name alone does not establish a release +range. Check command help before treating an exit code as success or failure; +check modes may use a nonzero result for drift as well as errors. + +The normal GitHub lifecycle prepares the version PR. Applying a local version +transaction requires existing consolidation authority and the same reviewed +options as the preview. Preserve exact committed input bytes, custom-template +trust requirements and any PR-resolution setting required by that release. +Verify only central history and consumed-fragment deletions are changed; no +technical plan file or new `Unreleased` section belongs in the version PR. + +If interrupted, preserve the recovery journal and remaining files. Follow that +release's recovery procedure with the same inputs. Do not edit a journal, delete +newer notes or replace history merely to force a retry. + +## Additional maintenance and publication + +Backfill, format, notes extraction and custom templates are optional operations. +Discover their actual commands and read that release's guide before using them. +Keep published descriptions, unknown dates, existing references and pending +fragments intact as required by the operation. Executable PHP templates require +explicit trust. Preview mutations and review the resulting diff. + +Publication requires separate authority for an exact approved full commit SHA. +Read that release's publication guide and command help, preview when supported, +and supply the verified target explicitly. Use the application's approved +credential mechanism. Verify the approved version, notes, consumed fragments, +tag and source commit; PR text or a moving branch is not transaction evidence. +Preparation and publication must use the same policies that affect the output. + +Do not move conflicting concrete tags or overwrite divergent published notes. +After an uncertain response, inspect remote evidence and follow the selected +release's idempotent recovery route under the existing authority. Report what +was actually published or recovered separately from successful validation. diff --git a/.agents/skills/fast-forward-changelog/references/php-api.md b/.agents/skills/fast-forward-changelog/references/php-api.md new file mode 100644 index 0000000..2dc88e4 --- /dev/null +++ b/.agents/skills/fast-forward-changelog/references/php-api.md @@ -0,0 +1,40 @@ +# Optional PHP integration + +Load only when the application needs injected services instead of the CLI or +GitHub lifecycle. CPX executes a separate tool; it does not provide an application +autoloader. Inspect the application's Composer installation with +`composer show fast-forward/changelog`, then read the PHP integration/API guide, +manifest requirements and migration notes from that exact release tag. + +Use the installed namespaces and method signatures. Keep the application's +existing command loader, container precedence and secret mechanism. Expose only +the needed domain services and inject external boundaries in application tests. +Resolving a service or writing a fragment does not authorize commits or releases. + +## Bundled contribution example + +The complete [contribution program](../examples/contribution.php) illustrates the +`0.1.0` PHP API: explicit consumer autoloader and target directory, provider +composition, fragment creation and validation. It is an optional example, not +the contract for every later release. Before using it with another version, +compare its imports/signatures with that release's documented API or use the +example shipped with the selected release. + +For a matching API, run it first in a disposable target: + +```sh +php .agents/skills/fast-forward-changelog/examples/contribution.php ./vendor/autoload.php /absolute/project +vendor/bin/changelog check --no-interaction +``` + +Success prints `Contribution example PASS`; exactly one +`.changelog/php-api-example.md` is created, with no commit or central-history +change. A repeated run must expose the filename collision rather than overwrite +the file. Missing inputs and runtime failures remain nonzero. Do not report +success after a failed write or ignore the validation report. + +Keep PR/issue/author fields absent unless verified. Service access follows the +same fragment, version-transaction and publication boundaries as the selected +release's CLI. Learn optional configuration, planner/history services and +approved-commit publication from that version's documentation when the task +requires them; do not load an API inventory for an ordinary fragment task. diff --git a/.changelog/adopt-fast-forward-changelog.md b/.changelog/adopt-fast-forward-changelog.md index e32ec94..7e9d1af 100644 --- a/.changelog/adopt-fast-forward-changelog.md +++ b/.changelog/adopt-fast-forward-changelog.md @@ -3,4 +3,4 @@ category: changed type: patch --- -Adopt independent Fast Forward Changelog fragments and reviewed version pull requests, and reconcile historical publication dates and delivery pull-request references. +Adopt independent Fast Forward Changelog fragments and reviewed version pull requests with repository-local contributor instructions, and reconcile historical publication dates and delivery pull-request references. diff --git a/.gitattributes b/.gitattributes index a9c8ab2..f728ef4 100755 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ * text=auto /.github/ export-ignore +/.agents/ export-ignore /tests/ export-ignore /AGENTS.md export-ignore /.docheader export-ignore diff --git a/.gitignore b/.gitignore index c058363..3fb9a53 100755 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ tmp/ vendor/ composer.lock *.cache -.agents/ +.agents/* +!.agents/skills/ +.agents/skills/* +!.agents/skills/fast-forward-changelog/ diff --git a/docs/maintenance/changelog-adoption.md b/docs/maintenance/changelog-adoption.md deleted file mode 100644 index bb90d81..0000000 --- a/docs/maintenance/changelog-adoption.md +++ /dev/null @@ -1,63 +0,0 @@ -# Changelog adoption and Release reconciliation - -## Selected runtime and workflow contract - -Investigated on 2026-10-18. Latest stable release: [v0.2.0](https://github.com/php-fast-forward/changelog/releases/tag/v0.2.0), published 2026-10-18T07:15:48Z; tag and commit: `2d3f84fa9e5350f03636153fdb3ae2a822e5ae60`. -The maintainer explicitly selected the moving `@v0` workflow channel. The live `refs/heads/v0` resolved to that same SHA during validation. This is an intentional 0.x update policy, including potentially breaking minor updates, rather than an immutable pin. - -Read the release's README, consumer SKILL.md, caller, reusable lifecycle and check/version workflows, CLI/history/policy contracts. The CPX cache already contains exactly v0.2.0 at this source reference; its binary runs under PHP 8.5.11. CPX itself could not write its host metadata lock in the sandbox, so validation invokes this verified installed binary directly. No project dependency or global skill was installed. Local CLI commands must select version 0.2.0 explicitly rather than copy older ranges embedded in the upstream README. - -The consumer uses English documentation, `main`, `CHANGELOG.md`, `.changelog`, the `v` tag prefix, tag-based history and `resolve-pull-requests: true`. All planning, preparation, checks and any future publication must use these same settings. Ordinary fragments omit unknown PR metadata; the resolver can prove it after merge without a follow-up fragment edit. - -Existing workflows use GitHub-hosted Linux/Windows runners and default GitHub tokens. Repository Actions variable and secret inventories were empty; no existing changelog caller, managed version PR, Changesets directory or pending fragments were found. No unrelated workflow or Composer dependency was removed. The new lifecycle uses Linux with Docker/Bash/Git, the default GitHub Bot, managed branch `changelog/version`, and the established title `chore(release): prepare v{next_version}`. `publish: false` is deliberate. A default-token version PR may need workflow approval under GitHub policy; configure an App only through a separately approved credential setup. - -## Evidence and historical corrections - -Baseline: `038deddd474e87528528539d0668d060939ef973` (main and v1.7.0). Inspected the complete first-parent history and each successive tag interval, merged PR API records and the affected source/workflow diffs. All 13 tags resolve to commits reachable from main, and match their remote refs; 13 corresponding GitHub Releases exist. The first interval is repository root through v1.0.0; every later interval is the previous listed version through that tag. Annotated tags are peeled to commits in the table. - -- Preserve the introduction, all historical descriptions and all 13 correct version-link definition lines byte for byte. Remove only the empty legacy Unreleased heading and its HEAD link. No pending descriptions need migration. -- Use the UTC calendar date of GitHub `published_at` consistently. v1.2.0 was published at 2025-05-24T02:06:08Z (2025-05-23 locally); v1.7.0 at 2026-10-17T01:39:54Z (2026-10-16 locally). The old dates can be explained by local time, but were not the UTC publication dates. No timestamps are rewritten on GitHub. -- Move directory-path support in `config()` from 1.3.0 to 1.0.0: it is already in `v1.0.0:src/functions.php`; `v1.2.0..v1.3.0` does not change that file. Preserve its description without inventing a PR. -- PR #2 delivered PhpFileConfig, persistence safeguards and the falsy-value fix; #3 delivered the compatibility alias and Sphinx underline correction. The historical notes imported by #2 do not make it the delivery PR for older behavior. PHP 8.3/Sphinx baseline work was integrated directly, so those notes retain no PR. -- PR #1 delivered ArrayAccess/remove(); #4 the mascot; #5 both status fixes; #7 list aggregation. Reports deployment required #9, #10 and #11, all within v1.5.0..v1.5.1. The version-preparation role of #9 is not used to attribute unrelated changes. -- v1.6.0: #17 native storage; #13 Wiki publication. #14 removes an obsolete Wiki submodule pointer; retain the existing user-facing Wiki summary rather than add an implementation-only release bullet. -- v1.7.0: #34 skill; #31 cache changes; #32 alias removal; #35 provider normalization; #33 file persistence; #37 directory discovery; #30 container/portable functions; #36 file-cache documentation. #38 only prepared release notes and is not their delivery PR. -- Earlier direct commits prove the existing 1.0.0–1.3.0 descriptions. No corresponding delivery PR was proved; no hashes, authors or issues are inserted as substitute note references. No duplicate versions or conflicting identities were found. Old promises, including the 1.5.0 compatibility alias subsequently removed in 1.7.0, remain in their historical context. - -## Reconciliation inventory - -File status is the proposed canonical history. Every existing Release requires a body-only update; no creation, deletion, tag move, target change, asset change or draft/prerelease change is planned. All are non-draft, non-prerelease and have no assets at capture time. Dates below are publication evidence, not commit timestamps. - -| Tag | Peeled commit SHA | Version | Proven publication (UTC) | CHANGELOG | GitHub action | -| --- | --- | --- | --- | --- | --- | -| v1.7.0 | `038deddd474e87528528539d0668d060939ef973` | 1.7.0 | 2026-10-17T01:39:54Z | present; reviewed | PATCH body | -| v1.6.0 | `3282f877808a23382cd41f4ed30946fcc10ec515` | 1.6.0 | 2026-10-16T19:19:22Z | present; reviewed | PATCH body | -| v1.5.1 | `7a85fa548bb10f81550d4d9b576c337a765f73ca` | 1.5.1 | 2026-10-09T18:05:12Z | present; reviewed | PATCH body | -| v1.5.0 | `bee3ac57a9f7f0a377022d5723c71e7c9bea4242` | 1.5.0 | 2026-09-17T23:46:33Z | present; reviewed | PATCH body | -| v1.4.0 | `930434f7f7aaf02d551eb53ab86ed25e6511522c` | 1.4.0 | 2025-06-11T14:38:34Z | present; reviewed | PATCH body | -| v1.3.0 | `eccc7bb8d0cd566b4001462c8e6726b295f0da44` | 1.3.0 | 2025-05-24T16:41:44Z | present; reviewed | PATCH body | -| v1.2.0 | `89385178faece2292ee8f3fbeb0eb953f70a120f` | 1.2.0 | 2025-05-24T02:06:08Z | present; reviewed | PATCH body | -| v1.1.4 | `36ae2c0da0f895adc5b105f08b0871c4a841a7ec` | 1.1.4 | 2025-04-20T15:19:41Z | present; reviewed | PATCH body | -| v1.1.3 | `795abbee1b5bba6ef08e1d2ce9fdd7e32f5c68da` | 1.1.3 | 2025-04-20T03:38:06Z | present; reviewed | PATCH body | -| v1.1.2 | `53818d6da40ff061c603abedaf656834ca179b3e` | 1.1.2 | 2025-04-19T15:46:55Z | present; reviewed | PATCH body | -| v1.1.1 | `f8d530ece45dee2a3308b58a32fe9a46ae6baa14` | 1.1.1 | 2025-04-18T23:32:42Z | present; reviewed | PATCH body | -| v1.1.0 | `d54a476287f280b8c3f964a279299778acd089e2` | 1.1.0 | 2025-04-18T22:46:15Z | present; reviewed | PATCH body | -| v1.0.0 | `6a83377fdaff883b7825760cd9191b5f5084aa88` | 1.0.0 | 2025-04-18T19:10:42Z | present; reviewed | PATCH body | - -## Concrete Release differences and authorized follow-up - -The new body for each row is exactly the output of the selected runtime's `notes VERSION --source=tags --repository=php-fast-forward/config`. All body links are inline; no footer context is required. The version heading and document footer are excluded by the official extractor. New PR links are added to 1.4.0–1.7.0; older empty/single-line bodies receive the existing curated categories and descriptions. 1.3.0 loses the misattributed directory-support note, which moves to 1.0.0. 1.6.0 loses its extra **Full Changelog** line. No automatic authors, commit lists or second generated change list are included. - -The concrete JSON plan, unified diffs, original/desired SHA-256 values and Release metadata are retained with the task review artifacts, outside this repository. Recreate that evidence after integration from a clean, fetched checkout using the exact reviewed CLI's `notes` command and read-only GitHub API calls. Verify every remote tag (including annotated tag peeling), ancestry, canonical HEAD and Release metadata before reviewing the minimal body-only requests. - -The library's publication validator is intended for approved version transactions and refuses divergent published notes. It is not used to overwrite historical Releases. After explicit reconciliation authorization AND integration of the canonical history, fetch and verify the merged SHA, reviews, unresolved threads and gates. Extract the notes there, compare the fresh Release snapshots, and review each request. Execute only the approved `PATCH /repos/php-fast-forward/config/releases/{id}` with JSON `{"body": ""}`; do not send tag_name, target_commitish, name, flags or assets. If a missing Release is found later, stop and review that case separately, including that creation now does not establish historical publication. Finally GET each affected Release and compare body bytes plus protected metadata/assets. Preserve any concurrent changes and investigate instead of overwriting them. - -## Maintenance authority and validation - -This is a normal human migration PR with one independent patch fragment. Central-history edits use the library's legitimate `changelog-maintenance` policy: a live label grant by a GitHub User with verified maintain/admin permission, matching numeric ID/login and the exact PR HEAD. The investigating account is `coisa`, User ID `426835`; the collaborator API reports `permission: admin`, `role_name: admin`. Verify the actual label timeline grant and current permission again for the published head. No trailers, Bot signatures or managed-branch ownership are fabricated. The caller does not disable any check. On initial adoption, privileged feedback only becomes available from the approved base workflow after integration. - -No managed version PR exists to regenerate. After approved integration, let the automation generate its own version PR and independently verify Bot ownership, signed head, current base and consumed fragments. Do not manually populate or edit its branch. - -Validation results are recorded in the PR after the final SHA is published. Applicable consumer cases are manual history, correct reference footer, empty pending section, published versions, divergent Releases, known/unknown PR references and next-version generation. No-file/no-tag and Changesets/mixed cases do not describe this consumer; relevant upstream coverage at the selected SHA is in `tests/History/HistoryCodecTest.php`, `HistoryReferenceBuilderTest.php`, `Import/HistoryImporterTest.php`, `tests/Release/ReleaseHistoryConsolidatorTest.php`, `tests/Integration/AutomaticPullRequestReferenceTest.php`, `tests/Console/Command/NotesCommandTest.php` and `tests/Automation/Policy/PullRequestPolicyTest.php`. Those tests are evidence of upstream coverage, not a claim they ran in this consumer. - -No unresolved version/date identity remains after choosing UTC dates. Unproved historical PR identities deliberately remain absent. Merge, generated-version integration, publication enablement and all real Release writes still require explicit authorization.