diff --git a/.changelog/bundled-consumer-skill.md b/.changelog/bundled-consumer-skill.md new file mode 100644 index 0000000..bd14356 --- /dev/null +++ b/.changelog/bundled-consumer-skill.md @@ -0,0 +1,6 @@ +--- +category: added +type: minor +--- + +Bundle the fast-forward-http-message consumer skill with executable examples, optional installation guidance and Composer discovery metadata. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f997ef9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,15 @@ +# fast-forward-http-message maintenance + +This repository owns `fast-forward/http-message`. Read Composer metadata, source and +tests before changing public integration. Preserve unrelated work. Run +`composer install`, `composer validate --strict` and `composer dev-tools`. +Keep merge, release and deployment decisions with the maintainer. Ordinary +source/resources share this root contract; consumer procedures live in SKILL.md. + +## Bundled consumer skill + +Read [the consumer skill](resources/skills/fast-forward-http-message/SKILL.md) when changing application integration +examples, consumer guides or the skill. Keep its version contract, references +and executable examples aligned with the supported public API. The canonical +distributable source is `resources/skills`; it needs no installed copy or host +alias. Repository maintenance continues to follow this AGENTS.md. diff --git a/README.md b/README.md index 8ceb960..0a2ae68 100644 --- a/README.md +++ b/README.md @@ -180,3 +180,12 @@ Feel free to open a [GitHub Issue](https://github.com/php-fast-forward/http-mess - [PSR-7](https://www.php-fig.org/psr/psr-7/) - [PSR-15](https://www.php-fig.org/psr/psr-15/) - [nyholm/psr7](https://github.com/Nyholm/psr7) + +## Consumer agent skill + +The package includes [fast-forward-http-message](resources/skills/fast-forward-http-message/SKILL.md) for application integration. +It supports `fast-forward/http-message:^1.5`; inspect the installed version. +See [skill installation and lifecycle](resources/skills/fast-forward-http-message/references/installation.md) for +optional project synchronization with `llm/skills ^1.15`, guarded manual +project/global copies and a pinned Skills CLI alternative. Shipping files does +not install tooling or activate the skill in an agent session. diff --git a/composer.json b/composer.json index 384186f..a53e535 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,9 @@ "fast-forward/dev-tools": "dev-main", "llm/skills": "^1.15" }, + "suggest": { + "llm/skills": "Optionally synchronize the bundled consumer skill in your project." + }, "minimum-stability": "stable", "autoload": { "psr-4": { @@ -54,6 +57,9 @@ }, "grumphp": { "config-default-path": "vendor/fast-forward/dev-tools/grumphp.yml" + }, + "skills": { + "source": "resources/skills" } }, "scripts": { diff --git a/resources/skills/fast-forward-http-message/LICENSE b/resources/skills/fast-forward-http-message/LICENSE new file mode 100644 index 0000000..aa2c238 --- /dev/null +++ b/resources/skills/fast-forward-http-message/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2025 Felipe Sayão 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/resources/skills/fast-forward-http-message/SKILL.md b/resources/skills/fast-forward-http-message/SKILL.md new file mode 100644 index 0000000..cad7ff4 --- /dev/null +++ b/resources/skills/fast-forward-http-message/SKILL.md @@ -0,0 +1,79 @@ +--- +name: fast-forward-http-message +description: Use Fast Forward PSR-7 payload responses, JSON streams, status and header helpers, preserving immutability and propagating encoding failures. +license: MIT; see LICENSE +compatibility: Supports fast-forward/http-message ^1.5 and PHP ^8.3. Requires Composer and declared extensions; inspect the installed version. Earlier releases are outside this contract. +metadata: + author: Fast Forward + api-baseline: fast-forward/http-message ^1.5 +--- + +# Build immutable HTTP responses + +Use this skill for an application consuming `fast-forward/http-message:^1.5`. +Read its instructions, manifest/lockfile and installed source before editing. +Run `composer show fast-forward/http-message`. For versions outside this contract, +inspect matching documentation; do not silently upgrade the application. + +## First working integration + +Requires PHP ^8.3, Composer's application autoloader and this package's runtime +dependencies. +With authority to install the dependency: + +```sh +composer require 'fast-forward/http-message:^1.5' +``` + +Create `app.php` in the application root alongside composer.json/vendor: + +```php + false]); +$changed = $original->withPayload(['ready' => true])->withStatus(201); +if ($original->getPayload() !== ['ready' => false] || $changed->getPayload() !== ['ready' => true] + || $changed->getStatusCode() !== 201 || !str_starts_with($changed->getHeaderLine('Content-Type'), 'application/json') + || json_decode((string) $changed->getBody(), true, flags: JSON_THROW_ON_ERROR) !== ['ready' => true] + || (new RedirectResponse('/login'))->getStatusCode() !== 302 + || Authorization::parse('unsupported fixture') !== null) { + throw new RuntimeException('Response contract differs.'); +} + +echo "integration ready\n"; +``` + +Run `php app.php`. It throws on a wrong result; success prints +`integration ready`. It uses no production data and starts no server. + +## Wiring and failure boundaries + +This library uses `FastForward\Http\Message`; no container/provider is required. Construct JsonResponse with an application payload, use withStatus/withHeader/withPayload and retain the returned object. The application server or emitter must write the response; constructing it sends nothing. + +JsonResponse sets an application/json Content-Type with charset and uses JsonStream. withPayload returns a new stream/response, but PSR-7 streams can themselves be mutable: do not treat a shared stream's cursor or write() as an immutable payload update. Top-level resources raise InvalidArgumentException; malformed UTF-8 and other JSON encoding failures raise JsonException. Keep the original response when testing mutation boundaries. + +TextResponse and HtmlResponse set their corresponding content types, EmptyResponse is 204, RedirectResponse uses 302 or 301 for its permanent flag. RequestMethod/StatusCode and Header helpers are independent utilities. Authorization::parse returns null for missing/unknown or malformed supported credentials; parsing is not authentication. Never log credentials. Digest/AWS parsing uses mb_trim, which is native only from PHP 8.4: on PHP 8.3 install an appropriate polyfill before using those optional routes. The core JSON example needs no such route. + +## Verify integration + +Run the [complete example](examples/integration.php) against the application's +autoloader in a disposable process. It checks success and negative cases: + +```sh +php /path/to/fast-forward-http-message/examples/integration.php ./vendor/autoload.php +``` + +Expected final marker: `fast-forward-http-message: ok`. Keep application-specific regression +tests in its existing PHPUnit suite, with each fixture class in its same-named +file. File copying, host discovery and completed agent tasks are different checks. +For optional copying/synchronization load +[installation and lifecycle](references/installation.md). diff --git a/resources/skills/fast-forward-http-message/examples/integration.php b/resources/skills/fast-forward-http-message/examples/integration.php new file mode 100644 index 0000000..a279d75 --- /dev/null +++ b/resources/skills/fast-forward-http-message/examples/integration.php @@ -0,0 +1,27 @@ + false]); +$changed = $original->withPayload(['ready' => true])->withStatus(201); +if ($original->getPayload() !== ['ready' => false] || $changed->getPayload() !== ['ready' => true] + || $changed->getStatusCode() !== 201 || !str_starts_with($changed->getHeaderLine('Content-Type'), 'application/json') + || json_decode((string) $changed->getBody(), true, flags: JSON_THROW_ON_ERROR) !== ['ready' => true] + || (new RedirectResponse('/login'))->getStatusCode() !== 302 + || Authorization::parse('unsupported fixture') !== null) { + throw new RuntimeException('Response contract differs.'); +} +try { + new JsonStream("\xB1"); + throw new RuntimeException('Invalid UTF-8 unexpectedly encoded.'); +} catch (JsonException) { +} + +echo "fast-forward-http-message: ok\n"; diff --git a/resources/skills/fast-forward-http-message/references/installation.md b/resources/skills/fast-forward-http-message/references/installation.md new file mode 100644 index 0000000..40a898e --- /dev/null +++ b/resources/skills/fast-forward-http-message/references/installation.md @@ -0,0 +1,167 @@ +# Optional installation of fast-forward-http-message + +`fast-forward/http-message:^1.5` includes this complete package under +`resources/skills/fast-forward-http-message`. Run `composer show fast-forward/http-message` +and inspect that path before copying. Earlier releases are outside this skill +contract. Composer suggestions do not install anything automatically. Library +installation, discovery-root copies and session activation are separate steps. + +## Team project with llm/skills + +Use Composer 2 and optionally add `llm/skills:^1.15` as development tooling. +Start with a project target for reviewable team configuration. Preserve existing +settings/aliases/approvals when merging these keys into `skills.json` **before** +installing or enabling the plugin: + +```json +{ + "target": ".agents/skills", + "aliases": [], + "discovery": false, + "auto-sync": true, + "vendor-sources": false, + "dependencies": { + "composer": { + "trusted": ["fast-forward/http-message"], + "trusted-replace": true + } + } +} +``` + +This replaces implicit dependency trust with a narrow donor allowlist, disables +remote vendor sources and requests no host aliases. A deliberate +`fast-forward/*` allowlist grants broader organizational trust. Use the actual +host's project discovery root; Claude Code can choose `.claude/skills`. +Do not erase intentional aliases or other donors in an existing manifest. + +If the application owner has authorized this plugin's execution, merge its +root approval, install it and inspect the report: + +```sh +composer config allow-plugins.llm/skills true +composer require --dev 'llm/skills:^1.15' +composer skills:show +``` + +The donor only suggests the tool; it neither requires it at runtime nor grants +this approval on behalf of consumers. Commit the chosen manifest, skills.json, +application lockfile and intended project copies according to project policy. +Approved Composer install/update operations then perform automatic sync. + +For manual timing, choose `auto-sync:false` before installation and use: + +```sh +composer skills:show +composer skills:update fast-forward/http-message --dry-run +composer skills:update fast-forward/http-message +``` + +Explicitly naming a donor selects/trusts it for that run. Test automatic trust +without a package argument when that policy is what matters. With Composer +2.10.3 and llm/skills 1.15.0, `--no-scripts` alone still permits automatic copy; +`auto-sync:false` disables automatic sync and `--no-plugins` stops plugin loading. +Verify these behaviors again after changing tool versions. + +## Update and removal boundaries + +A 1.15 sync overwrites incoming same-path files. It retains extra local files +and files removed from the donor's next version. Removing a donor or uninstalling +the plugin does not delete its existing instruction copies. Regard the copy as +donor-owned; keep deliberate local instructions in a separate skill name. +Record the installed donor/tool versions and review diffs before each refresh. + +Inspect cleanup without applying it: + +```sh +composer skills:update --clean --dry-run +``` + +An unscoped clean can remove all target skills, including handwritten ones. +Scoped clean reinstalls the current selection; it cannot locate an absent donor +by historical ownership. Inventory affected directories, back up outside +discovery, and obtain authority before any destructive cleanup. Do not add a +root-wide clean hook. + +## Guarded manual copying + +Choose the existing project root, then copy all files together: + +```sh +skill_source="vendor/fast-forward/http-message/resources/skills/fast-forward-http-message" +skill_target=".agents/skills/fast-forward-http-message" +test -f "$skill_source/SKILL.md" && +test ! -e "$skill_target" && +test ! -L "$skill_target" && +mkdir -p "$(dirname "$skill_target")" && +cp -R "$skill_source" "$skill_target" +``` + +Both existing directories and dangling links are refused. Global use is an +explicit alternative: set skill_target to +`$HOME/.agents/skills/fast-forward-http-message` for Codex or the configured +`$HOME/.claude/skills/fast-forward-http-message` for Claude Code. +Do not create a symlink into replaceable vendor content. A globally installed +Composer plugin does not constitute a global instruction copy. + +For a selected manual update/removal, first move only that package into a unique +backup outside discovery: + +```sh +skill_backup="$HOME/.agent-skill-backups/fast-forward-http-message-$(date +%Y%m%d%H%M%S)-$$" +test -d "$skill_target" && +test ! -L "$skill_target" && +test ! -e "$skill_backup" && +test ! -L "$skill_backup" && +mkdir -p "$(dirname "$skill_backup")" && +mv "$skill_target" "$skill_backup" +``` + +For an update review the new source, then repeat the guarded copy. To roll back, +archive any replacement and restore the saved whole directory to the same +target. A manager-owned copy may return on its next sync until the consumer +changes that donor's trust/dependency configuration. Keep backups out of every +discovery directory. + +## Optional Skills CLI + +Requires npm, Node.js 22.20.0+ and Git for repository sources. The reviewed pin +is skills@1.7.2; evaluate version changes separately. Choose only the actual host: + +```sh +npx --yes skills@1.7.2 add ./vendor/fast-forward/http-message/resources/skills/fast-forward-http-message --skill fast-forward-http-message --agent codex --copy --yes +npx --yes skills@1.7.2 list --agent codex +``` + +Use `--agent claude-code` for that host. Add `--global` to both commands only +when global scope was selected. Preserve/commit intended project copies and the +CLI's project lockfile. For a remote source first set SKILL_REF to a reviewed +immutable commit: + +```sh +npx --yes skills@1.7.2 add "https://github.com/php-fast-forward/http-message/tree/$SKILL_REF/resources/skills/fast-forward-http-message" --skill fast-forward-http-message --agent codex --copy --yes +``` + +Review the new revision and back up local edits before repeating for an update. +Remove the chosen package/scope with: + +```sh +npx --yes skills@1.7.2 remove fast-forward-http-message --agent codex --yes +# Only for a selected global copy: +npx --yes skills@1.7.2 remove fast-forward-http-message --agent codex --global --yes +``` + +After every route inspect the complete files, refresh the agent session, +confirm the public name is discovered and perform a bounded consumer task. +A copy, listing and successful task are separate observations. + +Tool contracts: [llm/skills 1.15.0](https://github.com/roxblnfk/skills/tree/1.15.0), +[Composer plugin approval](https://getcomposer.org/doc/06-config.md#allow-plugins) +and [Skills CLI](https://github.com/vercel-labs/skills). + +The Skills CLI 1.7.2 removal report is not proof that the canonical project +`.agents/skills` directory disappeared. A Codex project exercise reported one +removal while that copied package remained. Inspect the actual target and CLI +lockfiles afterwards. If an obsolete copy remains, verify that no other host +uses it, then archive only that selected directory outside discovery using the +manual recovery procedure above. Do not run a blanket clean to compensate. diff --git a/tests/ConsumerSkillTest.php b/tests/ConsumerSkillTest.php new file mode 100644 index 0000000..58bb6de --- /dev/null +++ b/tests/ConsumerSkillTest.php @@ -0,0 +1,47 @@ + + * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://php-fast-forward.github.io/http-message/ + * @see https://github.com/php-fast-forward/http-message + * @see https://github.com/php-fast-forward/http-message/issues + * @see https://datatracker.ietf.org/doc/html/rfc2119 + */ + +namespace FastForward\Http\Message\Tests; + +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\Process; + +#[CoversNothing] +final class ConsumerSkillTest extends TestCase +{ + /** + * @return void + */ + public function testBundledConsumerIntegration(): void + { + $root = \dirname(__DIR__); + $process = new Process([ + \PHP_BINARY, + $root . '/resources/skills/fast-forward-http-message/examples/integration.php', + $root . '/vendor/autoload.php', + ], $root, [ + 'XDEBUG_MODE' => 'off', + ]); + $process->setTimeout(20); + $process->run(); + + self::assertTrue($process->isSuccessful(), $process->getErrorOutput() . $process->getOutput()); + self::assertStringContainsString('fast-forward-http-message: ok', $process->getOutput()); + } +}