sandermuller / boost-core
AI agent configuration sync for PHP projects. Author skills and guidelines once, publish to every agent.
Requires
- php: ^8.3
- composer-runtime-api: ^2.2
- composer/semver: ^3.4
- laravel/prompts: ^0.3
- nikic/php-parser: ^5.0
- opis/json-schema: ^2.4
- sebastian/diff: ^7.0
- symfony/console: ^7.0||^8.0
- symfony/finder: ^7.0||^8.0
- symfony/process: ^7.0||^8.0
- symfony/yaml: ^7.0||^8.0
Requires (Dev)
- composer/composer: ^2.6
- laravel/pao: ^1.0
- laravel/pint: ^1.29
- mrpunyapal/rector-pest: ^0.2
- nunomaduro/collision: ^8.0
- orchestra/testbench: ^9.0||^10.0||^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.0
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- rector/rector: ^2.4
- rector/type-perfect: ^2.1
- sandermuller/boost-skills: ^2.0
- spaze/phpstan-disallowed-calls: ^4.10
- stolt/lean-package-validator: ^5.7
- symplify/phpstan-extensions: ^12.0
- tomasvotruba/cognitive-complexity: ^1.1
- tomasvotruba/type-coverage: ^2.1
- 1.1.1
- 1.1.0
- dev-main / 1.0.x-dev
- 1.0.0
- 0.23.3
- 0.23.2
- 0.23.1
- 0.23.0
- 0.22.0
- 0.21.0
- 0.20.0
- 0.19.0
- 0.18.3
- 0.18.2
- 0.18.1
- 0.18.0
- 0.17.0
- 0.16.3
- 0.16.2
- 0.16.0
- 0.15.0
- 0.14.0
- 0.13.0
- 0.12.0
- 0.11.0
- 0.10.3
- 0.10.2
- 0.10.1
- 0.10.0
- 0.9.7
- 0.9.6
- 0.9.5
- 0.9.4
- 0.9.3
- 0.9.2
- 0.9.1
- 0.9.0
- 0.8.3
- 0.8.2
- 0.8.1
- 0.8.0
- 0.7.6
- 0.7.5
- 0.7.4
- 0.7.3
- 0.7.2
- 0.7.1
- 0.7.0
- 0.7.0-rc2
- 0.7.0-rc1
- 0.6.2
- 0.6.1
- 0.6.0
- 0.5.5
- 0.5.4
- 0.5.3
- 0.5.2
- 0.5.1
- 0.5.0
- 0.4.0
- 0.3.4
- 0.3.3
- 0.3.2
- 0.3.1
- 0.3.0
- 0.2.0
- 0.1.2
- 0.1.1
- 0.1.0
- dev-dependabot/composer/stolt/lean-package-validator-tw-5.7or-tw-6.0
- dev-feat/conventions-schema
This package is auto-updated.
Last update: 2026-06-07 09:04:46 UTC
README
AI agent configuration sync for any PHP project. Write skills, guidelines, and commands once in
.ai/; boost-core publishes them to nine agents: Claude Code, Cursor, Copilot, Codex, Gemini, Junie, Kiro, OpenCode, Amp. No framework dependency.
Contents
- How sync works · Install · Quickstart · What you get
- Skill sources · Tag filtering · Commands · Skill rendering
- Automating the sync · Project Conventions · File ownership
- CLI reference · Environment variables · Versioning & stability
How sync works
You author three kinds of content under .ai/, and boost sync fans each out to
every agent you selected in withAgents(...). One source, many agent-native copies:
| You write in | What it is | boost sync fans it out to |
|---|---|---|
.ai/skills/ |
Agent Skills (<name>/SKILL.md) |
.{agent}/skills/<name>/SKILL.md per agent |
.ai/guidelines/ |
Always-loaded guidance | CLAUDE.md, AGENTS.md, GEMINI.md, Copilot file |
.ai/commands/ |
Slash-command prompt templates | Per-agent command dirs (see Commands) |
Skills and commands land in gitignored per-agent directories; the guidance files stay tracked. See File ownership for why.
Install
boost-core is the engine. You rarely install it directly. Instead you install
the family package (a thin wrapper that bundles boost-core with a curated
skill set) that matches what you're building, and it pulls boost-core in.
| You're building | Install | Ships |
|---|---|---|
| A PHP application (not a package) | sandermuller/project-boost |
App-dev skills — DDD layering, repository pattern, DI, domain modeling, legacy coexistence |
| A Laravel application | sandermuller/project-boost-laravel |
laravel/boost MCP coexistence + nine-agent fanout + tag filter + remote skills |
| A framework-agnostic Composer package | sandermuller/package-boost-php |
Package-author skills + lean / gitattributes commands |
| A Laravel package | sandermuller/package-boost-laravel |
Laravel-package skills + McpJsonEmitter |
| Your own skill bundle / tooling | sandermuller/boost-core directly |
Just the sync engine — you supply the skills ← you are here |
composer require --dev sandermuller/boost-core
Coexists with laravel/boost in Laravel
projects via project-boost-laravel.
Quickstart
vendor/bin/boost install # scaffold boost.php + pick agents, vendor allowlist, tags vendor/bin/boost sync # fan out to selected agents vendor/bin/boost sync --check # dry run — report drift, no writes
Config lives at boost.php (repo root) or .config/boost.php; see
File ownership for the layout details. A minimal config:
use SanderMuller\BoostCore\Config\BoostConfig; use SanderMuller\BoostCore\Enums\Agent; return BoostConfig::configure() ->withAgents([Agent::CLAUDE_CODE, Agent::CURSOR]);
boost-core runs no install-time code of its own. It's a plain library, not a
Composer plugin. Run vendor/bin/boost sync yourself (e.g. in CI), or wire the
autosync hook to re-sync on composer install.
What you get
laravel/boost |
boost-core |
|
|---|---|---|
| Framework scope | Laravel only | Any PHP (Laravel, Symfony, plain-PHP, packages) |
| Skill sources | bundled + .ai/skills/ |
.ai/skills/ + Composer packages (resources/boost/skills/) + withRemoteSkills() + withAllowedVendors() filter |
| Tag filtering | none | withTags() subset rule |
| Remote skill sources | none | withRemoteSkills() — GitHub bundles + path imports |
| User-scope sync | none | boost sync --scope=user for globally-installed CLI tools |
| Origin tracing | none | boost where + boost where --diff=<name> (host / vendor / remote / shadow) |
| Doctor / path-repo audit | none | boost doctor, boost doctor --check-versions |
.ai/commands/ fan-out |
none | per-agent argument transpilation across 7 emit targets |
| Project Conventions | none | JSONSchema-validated slot fill-in via boost validate / boost slots |
The MCP server (Model Context Protocol) + Laravel docs API are laravel/boost's domain, so boost-core defers
to them in Laravel projects (see
project-boost-laravel
for coexistence).
Skill sources
Skills come from three places, all resolved on the same composer install / update
lifecycle and fanned out side by side:
-
Host — your project's own
.ai/skills/. -
Vendor packages — any Composer package that ships
resources/boost/skills/<name>/SKILL.md. Allowlist the vendor to pick it up:return BoostConfig::configure() ->withAllowedVendors(['vendor/package']) ->withAgents([Agent::CLAUDE_CODE]);
This is how a team distributes one curated skill set across many repos: author once in a package, allowlist everywhere.
sandermuller/boost-skillsis one example of the pattern. -
Remote sources — GitHub repos shipping
.skillrelease bundles or skill subdirs, declared withwithRemoteSkills()(below).
Remote skill sources
use SanderMuller\BoostCore\Skills\Remote\RemoteSkillSource; return BoostConfig::configure() ->withAgents([Agent::CLAUDE_CODE]) ->withRemoteSkills([ // Bundle mode — fetch the named `.skill` release asset and unzip it. RemoteSkillSource::githubBundle('peterfox/agent-skills', 'v1.2.0', [ 'composer-upgrade', 'phpstan-developer', ]), // Path mode — fetch the repo tarball at a ref and extract named subdirs. // `.` covers a whole-repo-is-one-skill layout. RemoteSkillSource::githubPath('mattpocock/skills', 'main', [ 'grill-with-docs' => 'skills/engineering/grill-with-docs', ]), ]);
Each fetched skill fans out exactly like host and vendor skills: same layout,
same withTags() filtering, same withExcludedSkills(['<owner>/<repo>:<name>'])
deny-list. Removing an entry prunes its output on the next sync.
- Cache. Bundles and tarballs land under
${BOOST_CACHE_HOME:-${XDG_CACHE_HOME:-$HOME/.cache}}/boost/remote-skills/. Pinned refs (a tag or 40-char SHA) cache forever; moving refs (main, a branch) re-resolve every 24h. - Offline.
boost sync --checknever hits the network.boost doctorlists every source, flags moving refs, and reports cache presence. - Rate limit. Anonymous GitHub caps at 60 req/h. Set
BOOST_GITHUB_TOKEN(any token withpublic_reposcope) to lift it to 5000/h. Cold CI runs andboost doctorover many sources need it. - Trust. Sources are opt-in by full path:
peterfox/agent-skills:composer-upgradegrants access to nothing else in the repo. Pin to a tag or SHA in production; moving refs are convenient, but a source-side push silently changes what lands. Archive extraction rejects path traversal, absolute paths, symlinks, and oversized payloads (200 MB total / 50 MB per file / 10000 entries), and any violation rejects the whole source rather than extracting part of it. - Strict mode.
BOOST_REMOTE_STRICT=1escalates any source failure to a sync-aborting error. Default is warn-and-skip.
Publishing a source for remote consumption: treat the SKILL.md frontmatter
name as durable public API (renaming breaks moving-ref consumers), keep source
dirs symlink-free (extraction rejects any symlinked entry), and align
metadata.boost-tags with the family's tag vocabulary.
Tag filtering
Vendor skills can be scoped to projects that want them, so a project with no Jira
work never receives a jira-triage skill (and its description never pollutes
the agent's skill-selection index).
A skill declares tags in its SKILL.md frontmatter:
--- name: jira-triage description: Triage and label incoming Jira issues. metadata: boost-tags: "php jira" ---
A project declares the tags it wants in boost.php:
use SanderMuller\BoostCore\Enums\Tag; return BoostConfig::configure() ->withAgents([Agent::CLAUDE_CODE]) ->withTags([Tag::Php, Tag::Jira]) // Tag enum cases or raw strings ->withExcludedSkills(['acme/pack:unwanted-skill']) ->withExcludedGuidelines(['acme/pack:unwanted-guideline']);
The rule: a vendor skill ships only when every tag in its boost-tags is
among the project's withTags() (skillTags ⊆ projectTags). An untagged skill
always ships, so the feature is inert until skills and projects opt in.
withExcludedSkills() drops a specific vendor/package:skill-name regardless of
tags. Vendor guidelines filter the same way, tagged either by metadata.boost-tags
or a sidecar resources/boost/guidelines/.boost-tags.yaml manifest (for guidelines
that must stay frontmatter-free for laravel/boost).
The Tag enum is just a convenience; the vocabulary is open, and any string is a
valid tag. vendor/bin/boost tags lists every tag installed skills/guidelines declare,
which your withTags() filters out, and what to add to receive them;
boost install's interactive picker offers the same. When sync drops tagged skills
because withTags() is empty, it prints a one-line nudge at boost tags.
Warning
Adding a tag to an already-shipped skill is consumer-breaking: every project that hasn't declared that tag loses the skill. Treat it as a breaking change.
Use boost where to trace where every resolved skill, guideline, and command
comes from (host / vendor / remote), with host-override shadowing annotated
inline. boost where --diff=<name> prints a unified diff between a host override
and the vendor copy it shadows.
Commands
.ai/commands/*.md holds reusable prompt templates: the slash-command files
agents surface in their palette. boost sync fans each out to the seven agents
with a command surface:
| Agent | Command target |
|---|---|
| Claude Code | .claude/commands/ |
| Cursor | .cursor/commands/ |
| Copilot | .github/prompts/ (as <name>.prompt.md) |
| Junie | .junie/commands/ |
| OpenCode | .opencode/commands/ |
| Amp | .agents/commands/ |
| Kiro | .kiro/skills/<name>/SKILL.md (slash-command) |
Codex and Gemini have no committable command target boost-core can write into
(Codex's prompts are deprecated/personal-only, Gemini uses TOML). When
.ai/commands/ is populated and one of those agents is selected, boost doctor
prints the manual authoring path so the gap isn't silent. Override the source dir
with ->withCommandsPath(...).
Argument placeholders are transpiled per-agent. Author once using the canonical
syntax: $ARGUMENTS (unsplit), $1/$2/… (positional), $name (named, optionally
declared in frontmatter arguments:), and \$ escapes for literals. On sync,
boost-core converts each to the agent's native shape (Claude $0-indexed, Copilot
${input:…}, and so on). Cursor and Amp have no placeholder support and emit
verbatim with a warning. The bundled command-arguments skill documents the full
table.
Skill rendering
Skill files default to plain markdown (SKILL.md). For template-flavored content
(Blade, Twig, anything needing a render step), register a SkillRenderer in
boost.php:
use SanderMuller\ProjectBoostLaravel\Rendering\BladeRenderer; return BoostConfig::configure() ->withAgents([Agent::CLAUDE_CODE]) ->withSkillRenderers([new BladeRenderer]);
The dispatcher matches longest-extension-first, so a BladeRenderer claiming
blade.php handles SKILL.blade.php. The built-in PassthroughRenderer always
handles .md. Render failures default to warn-and-skip (recorded in
SyncResult::errors); BOOST_RENDER_STRICT=1 escalates the first failure to a
sync-aborting error. A source whose extension has no registered renderer
(e.g. a SKILL.blade.php with no BladeRenderer) is flagged by boost sync and
boost doctor. Register a renderer for it, or rename it to SKILL.md.
The SkillRenderer contract is @api (locked at 1.0). Plugin authors writing
renderers, FileEmitters, or a BoostWrapperContract should work from
PUBLIC_API.md, which pins the frozen contract surface.
Automating the sync
boost-core ships no Composer plugin, so a composer install re-sync is opt-in.
Pick the entry point that fits:
| Entry point | Use for |
|---|---|
BoostAutoSync::run |
post-install-cmd / post-update-cmd hooks — silent on a no-op |
BoostAutoSync::runWithSummary |
User-invoked scripts (composer sync-ai) — prints a summary always |
BoostAutoSync::syncUserScopeOnce |
A globally-installed CLI tool self-syncing its own bundled skills |
All three honor BOOST_SKIP_AUTOSYNC=1.
Composer hook (consumer project)
"scripts": { "post-install-cmd": ["SanderMuller\\BoostCore\\Scripts\\BoostAutoSync::run"], "post-update-cmd": ["SanderMuller\\BoostCore\\Scripts\\BoostAutoSync::run"], "sync-ai": ["SanderMuller\\BoostCore\\Scripts\\BoostAutoSync::runWithSummary"] }
run checks Event::isDevMode(), resolves the bin-dir, runs vendor/bin/boost sync,
surfaces non-zero exits through Composer's IO, and is silent on a true no-op
(wrote=0, deleted=0). Output appears only when something changed or errored.
runWithSummary prints the one-line success summary on every sync, including
the no-ops run keeps quiet (useful when debugging "did the hook fire?"). Both
work on Windows + Unix.
Important
On Laravel + project-boost-laravel,
use @php artisan project-boost:sync instead of BoostAutoSync::run. The
artisan path runs through the Laravel container, which bootstraps BladeRenderer
and delivers laravel/boost's bundled skills to every agent. The bare-CLI path
bypasses both. See project-boost-laravel's install guide for the canonical
scripts shape.
CLI tool you publish (self-sync from bin script)
A tool installed with composer global require keeps its own bundled skills
current by self-syncing from its bin script:
#!/usr/bin/env php <?php declare(strict_types=1); require __DIR__ . '/../vendor/autoload.php'; \SanderMuller\BoostCore\Scripts\BoostAutoSync::syncUserScopeOnce( packageRoot: dirname(__DIR__), packageName: 'your-vendor/your-tool', ); // ... the tool's own dispatch ...
syncUserScopeOnce() runs a user-scope sync the first time it sees a given
version, then writes a per-version sentinel so later runs are free.
syncUserScope() is the ungated form. Both never throw, so the tool keeps running
even if its sync fails.
After composer global require-ing skill-bearing packages, run
vendor/bin/boost sync --scope=user --all once to user-scope-sync every globally
installed package that ships resources/boost/skills/, into
~/.{agent}/skills/<vendor>__<package>/<skill>/SKILL.md. User scope publishes a
package's skills wholesale: there's no boost.php, so tag filters and the
vendor allowlist (both project-scope controls) don't apply. Removed packages are
reaped on the next --all run; see File ownership.
Project Conventions
Vendor skills often need project-specific context: a Jira key, a branch pattern,
a test runner. Project Conventions injects it via a JSONSchema slot fill-in.
Vendors declare slots in resources/boost/conventions-schema.json; consumers fill
them in boost.php:
return BoostConfig::configure() ->withAgents([Agent::CLAUDE_CODE]) ->withConventions([ 'jira' => ['project_key' => 'HPB'], 'github' => ['default_base_branch' => 'develop'], ]);
Skills consume a slot either with an inline <!--boost:conv path="…" mode="…"-->
token (resolved into the emitted file) or via the rendered ## Project Conventions
block in CLAUDE.md. boost validate --strict hard-fails CI on a leaked token;
boost slots / boost where --conventions audit what's set.
See docs/conventions.md for the full reference:
inline tokens, observability, legacy-ref migration, and migrating vendor skills.
File ownership
boost-core generates files into your repo and home directory and tracks what it owns in a manifest, so a sync never silently overwrites hand-written content:
- Guidance files (
CLAUDE.md,AGENTS.md,GEMINI.md, Copilot) are wholesale boost-owned, markerless, and regenerated from.ai/guidelines/on every sync, but kept tracked so output is reviewable in diffs. Author guidance in.ai/guidelines/, never by hand-editing the target. - Skill + command directories are gitignored (100% generated from
.ai/). - A file you've hand-edited (sha diverged from the manifest) is never blanked
or reaped. Adopting boost-core in a repo with an existing
CLAUDE.mdwon't wipe it. - Removing a vendor dep or de-selecting an agent reaps the now-orphaned files it owned.
See docs/file-ownership.md for the manifest,
lifecycle reap, the empty-assembly guard, .config/ layout + relocation, managed
.gitignore, and user-scope cleanup-on-remove.
CLI reference
| Command | Purpose |
|---|---|
boost install |
Scaffold boost.php (if missing) + interactive agent / vendor / tag picker |
boost sync |
Fan out skills / guidelines / commands to selected agents |
boost sync --check |
Dry run — report drift, no writes (offline; gate CI on this) |
boost sync --scope=user [--all] |
User-scope sync for globally-installed CLI tools |
boost where |
Origin-traced listing of every skill / guideline / command that would ship |
boost where --diff=<name> |
Unified diff (skill OR guideline) between a host override and the vendor copy |
boost where --conventions [--json] |
Effective resolved conventions slots + provenance + block keep/drop status |
boost doctor |
Offline health check — config, remote sources, cache, emitters, token leaks. Advisory only — exits 0 unless config fails to load |
boost doctor --check-versions |
Opt-in Packagist comparison for path-repo shadows (one HTTP call per package) |
boost doctor --check-conventions |
Report conventions slot status (missing, unknown, file-existence) |
boost doctor --check-stale-paths |
Read-only audit of the retired-paths registry — what the next sync would clean up |
boost tags |
List available tags + their unlock counts across allowlisted vendors |
boost validate [--strict] |
Validate withConventions([...]) + scan for leaked tokens (--strict fails CI) |
boost slots [--missing|--filled] |
List conventions slots, optionally filtered by fill state |
boost paths |
List path globs boost-core manages |
boost convert-conventions |
Legacy one-shot: extract 0.8.x marker YAML into boost.php (hidden, not a contract) |
Exit codes: 0 ok, 1 failure, 2 usage. boost doctor is advisory, so gate CI
on sync --check / validate --strict instead.
Environment variables
Every variable is opt-in; unset = default behavior.
| Variable | Effect |
|---|---|
BOOST_SKIP_AUTOSYNC=1 |
Skip the BoostAutoSync composer-hook sync entirely |
BOOST_SKIP_GITIGNORE=1 |
Skip managed .gitignore updates (handy for CI / ephemeral Docker installs) |
BOOST_GITHUB_TOKEN |
GitHub token (public_repo scope) — lifts remote-skill fetches from 60 to 5000 req/h |
BOOST_REMOTE_STRICT=1 |
Escalate any remote-skill source failure to a sync-aborting error (default: warn-and-skip) |
BOOST_RENDER_STRICT=1 |
Escalate the first skill-render failure to a sync-aborting error (default: warn-and-skip) |
BOOST_CACHE_HOME |
Override the remote-skill cache root (defaults to $XDG_CACHE_HOME / ~/.cache) |
Versioning & stability
boost-core follows Semantic Versioning. The promise covers the public surface only:
- Config authoring API —
BoostConfig::configure(), theBoostConfigBuilderwith*()methods, theAgent/Tagenums, andRemoteSkillSource. - CLI — command names, documented options, and exit codes.
- Composer hooks —
BoostAutoSync::run/runWithSummary(new parameters always optional-with-default). - Plugin contracts —
FileEmitter,SkillRenderer,BoostWrapperContractand their DTOs. Parameterless constructors only.
Everything marked @internal (the whole engine) and on-disk regenerable state
(the sync manifest, ledgers, runtime dirs) may change in any release. The full
committed surface is enumerated in PUBLIC_API.md. From 1.0.0
on, breaking changes land only in a MAJOR bump and are called out in
CHANGELOG.md and UPGRADING.md.
More
UPGRADING.md— breaking-change migrations between versionsCHANGELOG.md— full release history (releases page has per-version notes)CONTRIBUTING.md— dev setup, test conventions, pre-release gauntletPUBLIC_API.md— the frozen semver surface in full
Testing
composer test # full Pest suite (unit + integration, real composer-install subprocesses) composer test-coverage # with coverage report
Security
Email security issues to github@scode.nl rather than filing a public issue. See
SECURITY.md for the disclosure policy.
Credits
Heavily inspired by laravel/boost. It's the
framework-free sibling.
License
MIT. See LICENSE.

