sandermuller/package-boost
Deprecated: sandermuller/package-boost is split into successors. Use package-boost-php for framework-agnostic Composer packages, package-boost-laravel for Laravel packages, or project-boost for PHP apps. Legacy 0.15.x remains for existing installs.
package-boost:doctor --fix. Runs package-boost:sync --prune --prune-orphans and package-boost:lean under the hood, then rebuilds the report from the post-fix state. Exit code is driven solely by the post-fix DoctorReport::hasIssues() — sync's prune refusal warns but exits 0, so doctor's rebuilt report is the single authoritative status source.fix outcome contract. With --fix --format=json, the payload gains an additive top-level fix object recording each category's attempted (what doctor saw pre-fix) and resolved (what actually changed). Refusals — for example sync declining to prune a Copilot file with user content — surface as { attempted: true, resolved: false }, distinguishable from a noop ({ attempted: false, resolved: false }). schema stays 1 (additive change).DoctorReport::hasIssues() previously failed on any SKILL.md frontmatter issue, including third-party vendor packages the operator can't patch. It now mirrors SyncCommand::hasHostFrontmatterIssues: only issues under host .ai/skills/ or under package-boost's own bundled resources/boost/skills/ flip exit code. Third-party vendor issues remain rendered (as warnings) but exit-zero..github/copilot-instructions.md via bare file existence — including hand-authored Copilot files without the <package-boost-guidelines> tag, where sync --prune is a no-op. The flag now requires the tag, matching SyncCommand::warnAboutLegacyCopilotInstructions. Hand-authored Copilot files are no longer flagged.Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.14.0...0.15.0
Three new commands round out the package-boost toolbelt: a one-shot
doctor diagnostic, a managed-block .gitattributes writer (lean),
and a frontmatter-aware skill/guideline scaffolder (new). sync
gains --prune-orphans and a host-only SKILL.md frontmatter linter
in --check. Internal classes move under Console\Internal\ so the
public command surface is finally clearly demarcated.
package-boost:doctor command. Aggregates the checks that
were previously scattered across sync --check, install, and the
legacy / orphan warnings into a single report: configured +
effective agents, sync drift counts, SKILL.md frontmatter issues,
deselected-agent orphans, vendor skill collisions, MCP / Boost
detection, the legacy Copilot file, and the .gitattributes
managed block. Exits non-zero on any finding. --format=json emits
a stable shape (schema: 1) parseable by jq.package-boost:lean command. Idempotently writes a managed
# >>> package-boost (managed) >>> / # <<< package-boost (managed) <<< block into .gitattributes covering AI-era
export-ignore paths (.ai/, .claude/, .cursor/, .agents/,
.junie/, .kiro/, AGENTS.md, CLAUDE.md, GEMINI.md, …) so
composer archive / Packagist --prefer-dist tarballs stay lean.
User-authored entries outside the marker block are preserved
verbatim. --check fails CI on drift. The shipped lean-dist
skill teaches the validation side
(stolt/lean-package-validator); this command handles the write
side.package-boost:new command. Scaffolds .ai/skills/<name>/SKILL.md
or .ai/guidelines/<name>.md with frontmatter pre-filled.
Rejects collisions unless --force is passed, and validates the
name against the same kebab-case shape (^[a-z][a-z0-9-]*$) the
frontmatter linter enforces — a freshly scaffolded skill always
passes package-boost:doctor without further edits.sync --prune-orphans. Deletes generated artefacts for agents
that fell out of package-boost.agents: skill dirs are removed
wholesale (sync writes them in their entirety), guideline files
have just the <package-boost-guidelines> block stripped (the file
is deleted only when nothing but whitespace remains, so
user-authored content outside the block is preserved), and
.mcp.json has the laravel-boost entry removed when
claude_code is deselected. Replaces the prior warn-only
behaviour, which still ships as the default.SKILL.md frontmatter linter in sync --check. Host-authored
.ai/skills/<name>/SKILL.md files now fail --check when missing
required frontmatter (name, description) or when name /
directory disagree. Shipped and vendor skills surface the same
issues as warnings only; CI catches host issues before they ship..gitattributes managed block in this repo. Now self-hosting
package-boost:lean — the same managed block ships in this
repository's .gitattributes. stolt/lean-package-validator is
installed as a dev dep for archive validation.BoostDetector, DeselectedAgentArtifacts,
LegacyCopilotInstructions, SyncAction, SyncFormatter, SyncPlan,
SyncReporter, SyncSources, SyncWriter, plus new DoctorReport,
PackageRoot, SkillFrontmatter, and SyncPlanner, all move under
SanderMuller\PackageBoost\Console\Internal\. They were already
[@internal](https://github.com/internal) final readonly; the namespace move makes the boundary
visible at a glance. Anything outside Console\Internal\ is the
public surface..ai/skills/ source becomes useful to each tool the
moment it ships skill support — no re-author or re-sync required.boost:update deprecated alias. Hidden, deprecated alias of
package-boost:sync since 0.8.0. Anyone still invoking
boost:update should switch to package-boost:sync; the previous
release already emitted a deprecation warning on every invocation.composer update sandermuller/package-boost
vendor/bin/testbench package-boost:sync
vendor/bin/testbench package-boost:lean
vendor/bin/testbench package-boost:doctor
Re-syncing rewrites the <package-boost-guidelines> block and skill
dirs in every selected agent's directory. package-boost:lean
writes the managed .gitattributes block (idempotent — safe to
re-run). package-boost:doctor is a read-only diagnostic; run it to
surface any remaining drift in one shot.
Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.13.0...0.14.0
Package-boost now treats every Composer package as in-scope, not just
Laravel packages. The shipped foundation guideline no longer asserts
"This codebase is a Laravel package" unconditionally — generated
CLAUDE.md / AGENTS.md content is correct for framework-agnostic
adopters from the first sync.
app/, semver discipline, tests-as-spec, public API discipline) are unconditional. Testbench / php artisan / laravel/boost guidance lives under ## If your package targets Laravel. After re-syncing, framework-agnostic packages stop seeing Laravel claims in their generated guideline files; Laravel-targeted packages keep all the Laravel guidance — it just lives below the new H2.### Framework-agnostic packages README subsection. Walks through the verified Testbench-as-dev-dep recipe (composer require orchestra/testbench --dev, minimal testbench.yaml with laravel: '[@testbench](https://github.com/testbench)', run sync). Notes that .mcp.json generation is skipped automatically when laravel/boost isn't installed. Calls out the package-boost:install workbench-helpers requirement so framework-agnostic adopters don't hit a silent break after sync.Console\BoostDetector (final readonly, [@internal](https://github.com/internal)). Extracts the Laravel-Boost-presence check used by MCP sync. The container-binding seam (package-boost.boost-detector) is honoured only while runningUnitTests() is true, so a stray service provider in a downstream app can't flip MCP sync on or off — production behaviour stays anchored to the package graph (class_exists(BoostServiceProvider::class, false)).package-boost:install error message rewritten. When the workbench config path can't be resolved, the command now lists three concrete recovery paths (install Testbench + re-run via vendor/bin/testbench, skip install entirely and use the zero-config "all agents" default, or hand-edit workbench/config/package-boost.php) instead of a single bare "requires Orchestra Testbench" line.## If your package targets Laravel marker and scans the framework-agnostic preamble for nine claim-shape patterns (is a laravel, targets laravel package, assume a laravel, …) — phrasing variants of the original bug class are rejected regardless of exact wording. A second test exercises the genuine laravel-boost-not-installed MCP-skip branch through the new detector seam, dodging the suite-wide stub provider that had been masking it.composer update sandermuller/package-boost
vendor/bin/testbench package-boost:sync
Re-syncing rewrites the <package-boost-guidelines> block in every
selected agent's guideline file with the new framework-neutral
foundation. No code changes required — the upgrade is generated-output
only.
Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.12.0...0.13.0
skill-authoring skill — teaches the four things models routinely get wrong on a new SKILL.md: namespacing to avoid silent collisions, frontmatter that actually triggers auto-activation, the .ai/skills/ vs resources/boost/skills/ source-dir choice, and the package-boost:sync regeneration step. Documents all three real collision modes (host masks vendor, vendor masks vendor, vendor masks package-boost default) so authors can reason about precedence without reading the sync code.SyncSources behaviour. Prevents the shipped collision table from drifting from the code.ai-guidelines skill trimmed. Skill-authoring mechanics now defer to the new shipped skill; the trimmed version keeps only repo-specific dogfooding guidance and corrects the prior "commit generated files together" instruction (this repo gitignores them per release-automation).Ships a … skill bullet list had silently fallen 3 entries behind resources/boost/skills/ — ci-matrix-troubleshooting, cross-version-laravel-support, and now skill-authoring — all added.composer update sandermuller/package-boost
vendor/bin/testbench package-boost:sync
The first sync after upgrading writes the new skill-authoring skill to every selected agent's skill dir. Existing skills are unaffected.
Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.11.0...0.12.0
Three new vendor-shipped skills propagate to consumer Laravel packages
via package-boost:sync, teaching maintainers how to write good README
files, GitHub release bodies, and UPGRADING.md guides.
readme skill — teaches the two README shapes (stub vs comprehensive), required sections per shape, voice, and a canonical staleness-audit pattern. Distilled from a survey of 11 well-maintained Laravel packages.release-notes skill — defaults to GitHub's auto-generated format and overrides only when major/breaking. Includes a "Bookwork to cut" list (no Internals sections, no Why-now narrative, no empty Compatibility blocks, no Test-hygiene reports).upgrading skill — per-major sections in reverse-chronological order, version-comment-labelled before/after code, stable H2 anchors that release-notes' breaking-change bullets link to. Recognises four real filename conventions (UPGRADING.md, docs/upgrading.md, etc.) without prescribing one.## Breaking changes bullets each end with [UPGRADING.md#anchor], and the host-internal pre-release skill validates the contract via a new 5d matrix row that fires on breaking releases only.references/laravel-package.md carries ecosystem conventions and is loaded advisorily when composer.json shows laravel/framework / illuminate/* / Filament / Livewire / Nova deps.composer update sandermuller/package-boost
vendor/bin/testbench package-boost:sync
The first sync after upgrading writes the three new skills (and their references/ subdirs) to every selected agent's skill dir. Existing skills are unaffected.
Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.10.1...0.11.0
package-boost:install was writing the literal four characters
${indent} to the start of the rewritten 'agents' => ..., line,
producing invalid PHP in workbench/config/package-boost.php:
${indent}'agents' => ['claude_code', 'copilot'],
The preg_replace replacement string used ${indent} as a named-
group backreference, but PHP only honours positional backreferences
($1, ${1}) in preg_replace replacements — named ones are
emitted verbatim. Switched to $1 against the (now-unnamed) first
capture group; existing comments, indentation, and unrelated config
keys still survive the rewrite.
Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.10.0...0.10.1
Three guideline files (CLAUDE.md, AGENTS.md, GEMINI.md) and
six unique skill dirs:
| Agent | Guidelines | Skills dir |
|---|---|---|
| Claude Code | CLAUDE.md |
.claude/skills |
| Cursor | AGENTS.md |
.cursor/skills |
| GitHub Copilot | AGENTS.md |
.github/skills |
| Codex CLI | AGENTS.md |
.agents/skills |
| Gemini CLI | GEMINI.md |
.agents/skills |
| Junie | AGENTS.md |
.junie/skills |
| Kiro | AGENTS.md |
.kiro/skills |
| OpenCode | AGENTS.md |
.agents/skills |
| Amp | AGENTS.md |
.agents/skills |
.agents/skills is shared across Codex, Gemini, OpenCode, and Amp —
sync writes there once and dedupes.
package-boost:install commandInteractive picker for the agent set:
vendor/bin/testbench package-boost:install
Defaults pre-fill in this order: existing package-boost.agents
config → import from laravel/boost's boost.json if Boost is
installed → detection-marker scan against the project (.cursor/,
.kiro/, CLAUDE.md, etc.) → all 9. The selection persists to
workbench/config/package-boost.php via a single-line regex-replace
of the 'agents' => key, preserving comments and unrelated keys.
Non-interactive flags: --all, --agents=claude_code,cursor,
--no-import. Adversarial config shapes (multi-line array, missing
key, hand-customised formatting) refuse with a clear diagnostic.
agents config key// config/package-boost.php
'agents' => null, // null = all 9; or e.g. ['claude_code', 'cursor']
Unknown agent names trigger a non-fatal warning naming each typo and
listing the supported set, so a misconfigured selection is visible
without breaking sync. When claude_code is filtered out, MCP sync
is skipped (--check reports claude-not-selected) — per-agent MCP
serializers are out of scope for this release.
.github/copilot-instructions.mdUpstream Boost migrated Copilot guidelines from
.github/copilot-instructions.md into AGENTS.md. Package-boost
matches that — the legacy file is no longer written. Sync detects
the leftover with our <package-boost-guidelines> tag block and
warns. To remove it automatically:
vendor/bin/testbench package-boost:sync --prune
--prune refuses if the file has user content outside the block,
or if the block has been hand-edited / is stale relative to
current .ai/ sources. Run a regular package-boost:sync first to
refresh the block, then --prune to clean up.
When package-boost.agents is narrowed after a previous broader
sync (for example, dropping from "all" to ['claude_code']), the
generated files for the now-deselected agents stay on disk —
.cursor/skills/, GEMINI.md, .mcp.json, etc. Sync now scans for
these and warns:
WARN Generated artifacts exist for agents NOT in `package-boost.agents`:
- .cursor/skills/ (14 entries)
- GEMINI.md (contains <package-boost-guidelines> block)
- .mcp.json (laravel-boost mcpServers entry present)
These were synced under a previous selection. Re-include the agent
or delete the paths manually.
Auto-removal is intentionally not done — guideline files may carry user content outside the package-boost-guidelines block. Surface and let the user decide.
claude-not-selected skip reason--format=json adds a stable shape for the gated MCP case:
{ "mcp": { "action": "skipped", "reason": "claude-not-selected" } }
Joins the existing laravel-boost-not-installed reason. The
schema's skipped semantics are unchanged; only the reason
identifier is new.
.github/copilot-instructions.md is no longer written. Existing
consumers will see a warning on the next sync; clean up via
package-boost:sync --prune or by deleting the file manually. No
config or API change is required — Copilot reads AGENTS.md now,
which package-boost has been writing alongside CLAUDE.md since
0.7.0.
Agents\Registry is the single source of truth for agent paths,
detection markers, and selection filtering. The 9 entries are
frozen against laravel/boost@8ed9f84 (verified by direct source
read of src/Install/Agents/* and src/BoostManager.php:22-32).Agents\BoostImporter reads boost.json at the project root,
filters against the registry, returns null for missing /
malformed / unknown-only inputs.Console\DeselectedAgentArtifacts enumerates orphans across
skills, guidelines, and MCP for the warning above.Console\LegacyCopilotInstructions owns the legacy-file detect /
prune contract, with the prune-safety check that compares the
file's tag block to the freshly composed expected block.SyncCommand's class cognitive complexity stays under PHPStan's
budget by extracting the post-categories rendering and final-exit
decisions into named helpers.Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.9.0...0.10.0
Discovers skills and guidelines shipped by sibling packages.
package-boost:sync now walks vendor/*/*/resources/boost/ and
merges contributions between the shipped defaults and the host's
.ai/. First native path that doesn't rely on Laravel Boost's
Composer resolver, sidestepping the testbench-skeleton discovery
bug that blocks Boost's own scan under package development.
Any installed Composer package that ships
resources/boost/skills/<name>/SKILL.md or
resources/boost/guidelines/*.md is now picked up by
package-boost:sync automatically. Load order:
vendor/name).ai/For skills, later entries override earlier ones on name
collisions — host/.ai/skills/<name> always wins over a vendor
skill of the same name. For guidelines, each source
contributes its own block and they concatenate in load order,
separated by ---.
This means a package author can ship, for example, a
resources/boost/skills/fluent-validation-testing/SKILL.md
bundled with their library, and downstream consumers running
package-boost:sync will pick it up in their .claude/skills/
and .github/skills/ directories alongside their own content —
no extra wiring, no manual copy.
Two new keys in config/package-boost.php, both shipped with
sensible defaults so existing consumers see the behavior
activate on composer update with no publish step required:
'discover_vendor_packages' => true,
'excluded_vendor_packages' => [
'sandermuller/package-boost',
],
discover_vendor_packages — toggle off to restore 0.8.x
behavior (shipped + host .ai/ only). No consumer has asked
for this yet; the flag exists as an escape hatch.excluded_vendor_packages — skip specific packages by
vendor/name. Default excludes package-boost itself so a
transitively-installed copy can't double-ingest shipped
content through its own resources/boost/.Beyond the configurable exclude list, SyncSources::vendorDirs()
realpath-compares each vendor match against the shipped
resources/boost/<kind> directory and drops exact matches. This
is structural, not user-tunable — so even a consumer who
deliberately clears excluded_vendor_packages can't double-
ingest shipped skills through a symlinked dev checkout or a
transitive dep surfacing package-boost under its own vendor/
tree.
Laravel Boost's upstream package discovery reads
base_path('composer.json'), which under Testbench resolves to
the testbench-core skeleton with no require entries. That
means resources/boost/{skills,guidelines}/ content shipped by
third-party packages is invisible to Boost itself when run via
vendor/bin/testbench boost:install inside a package repo.
Package Boost's shipped-bundling approach (0.3.3+) carried
package-boost's own skills through this gap. Vendor discovery
(0.9.0) extends the same idea to arbitrary packages — any
dependency that ships resources/boost/ gets surfaced, whether
Boost is installed or not.
composer update sandermuller/package-boost
vendor/bin/testbench package-boost:sync
The first sync after upgrading may add skills or guidelines
contributed by your existing dependencies. Review the generated
.claude/skills/ / .github/skills/ / guideline blocks; if a
vendor contribution isn't wanted, add its vendor/name to
excluded_vendor_packages in a published config file.
If you previously relied on your .ai/skills/<name> containing
the only copy of a given skill name, behavior is unchanged —
host .ai/ still wins on collisions.
No breaking changes. No schema changes to --format=json
output — vendor contributions flow through the existing
skills / guidelines arrays indistinguishably from shipped
and host entries, so CI gates on the JSON drift report continue
to work.
Config file additions are forward-compatible: consumers on a
published 0.8.x package-boost.php keep working because
applyUserConfigOverrides uses array_replace (missing keys
retain shipped defaults).
SyncSources::vendorDirs() globs
vendor/*/*/resources/boost/<kind> with GLOB_ONLYDIR, sorts
by vendor/name, and filters via exclude list + realpath
guard.SyncSources::dirs() sequence between shipped and host
.ai/, so all of planSkills, planGuidelines, and
--check drift detection work without modification.tests/SyncCommandTest.php:
discovery, host-wins-on-collision, guideline merge,
--check drift reporting, discover_vendor_packages=false,
excluded_vendor_packages respect, and the self-mirror
realpath guard. Total suite: 53 passing, 184 assertions.Hygiene release. No code changes, no schema changes. Ships the repo housekeeping that accumulated since 0.8.0 — nothing here requires downstream action.
New Composer auto-sync hook section in the README under Composer
script, documenting three variants that pair well with the 0.4.0
--check gate:
[@php](https://github.com/php) vendor/bin/testbench package-boost:sync --check as a post-autoload-dump entry. Fails
the install if anything drifted.--check; friendlier but
leaves uncommitted changes on a dirty branch.--check --skills --guidelines so Boost-less packages don't see the "Laravel Boost
is not installed" warn on every composer run (direct response to
the js-store peer's 0.8.0 verification feedback).Cross-platform note: the hook form works on both posix (/bin/sh)
and Windows (cmd.exe). Chained shell operators (&&, ||) are
not portable across composer's shell layers — use separate array
entries if you need multiple steps.
Cross-linked from the shipped package-development skill's
Syncing section so discoverability works both from the README and
from the skill bundle downstream users see.
CHANGELOG.mdCHANGELOG.md now lives at the repo root, auto-maintained by a new
.github/workflows/update-changelog.yml workflow. On each published
GitHub release the workflow prepends the release body to
CHANGELOG.md and commits back to main via
stefanzweifel/changelog-updater-action. Seeded with entries from
v0.4.0 through v0.8.0.
README now links to CHANGELOG.md between How It Differs and
License.
README header carries four badges matching the sibling package's
style: Packagist version, test workflow status, code-style workflow
status, Laravel compatibility. Existing content-only badge (Laravel
Compatibility) updated to ?style=flat for visual consistency.
.ai/guidelines/ and .ai/skills/ are now committed:
release-automation (explains the
CHANGELOG.md automation), verification-before-completion
(evidence-before-claims rule, mirrored from laravel-fluent-
validation with a preamble note pointing at the canonical copy).ai-guidelines, backend-quality, bug-fixing,
code-review, codex-review, evaluate, implement-spec,
pr-review-feedback, pre-release, write-spec — 10 workflow
skills carried over from the sibling, adapted where they
referenced fluent-validation internals (backend-quality's
benchmark group → rector; bug-fixing's FluentRule example →
SyncCommand fixture; pr-review-feedback's GraphQL repo name;
pre-release rewritten for package-boost's matrix). The
autoresearch skill was deliberately skipped — its premise is
autonomous perf-optimization with a benchmark harness, which
package-boost doesn't have.testbench.yaml is now committed (previously gitignored) so
contributors can run vendor/bin/testbench package-boost:sync
without manual setup.
Generated outputs (CLAUDE.md, AGENTS.md, .github/copilot- instructions.md, .claude/skills/, .github/skills/) remain
gitignored — the sync-command tests exercise those exact filesystem
paths and would clobber any committed copies every pest run. Run
sync locally after composer install.
tests/SyncCommandTest.php::wipeArtifacts() now targets only
test-created fixtures (test-skill, keep-me, stale-skill,
test.md) so committed .ai/ content survives the suite. One test
(it ships foundation guideline even without a user .ai/guidelines directory) renames-and-restores the dogfood guidelines dir to
exercise the no-user-guidelines path that downstream consumers hit
before authoring their own content.
composer update sandermuller/package-boost
Nothing changes at runtime. Consumers see the README additions and
the new CHANGELOG.md link on the next visit to the repo.
If you want the auto-sync hook in your own package, add the
relevant post-autoload-dump variant from the README to your
package's composer.json scripts block.
No breaking changes. No schema changes. No config schema changes. Text and JSON sync output byte-compatible with 0.8.0.
Hygiene release. Ships a deprecation alias for the command name that keeps showing up in stale skill bundles.
boost:update → deprecated alias for package-boost:syncvendor/bin/testbench boost:update
# WARN boost:update is deprecated and will be removed in a future release. Use package-boost:sync instead.
# Skills:
# + .claude/skills/package-development
# ...
Several floating skill bundles reference a boost:update command
that never existed — the real command has always been
package-boost:sync. Before this release, typing boost:update
produced Laravel's generic Command "boost:update" is not defined
error with no hint at the actual name.
Now it runs the same thing package-boost:sync would, with a one-
line migration warning printed first. Hidden from artisan list so
users who already migrated don't see it. All options (--check,
--skills, --guidelines, --mcp, --show-unchanged,
--format=text|json) pass through unchanged.
Tracked in ROADMAP.md under a new Sunset section. Target
removal in 0.11.0 — three minor releases' worth of notice, since
dev-tooling adoption is slow. If you're still typing boost:update
by then, the warning will have been in your face for every run in
between.
composer update sandermuller/package-boost
No action required. If you have scripts or CI invoking
boost:update, update them to package-boost:sync at your leisure
— both work until 0.11.0.
No breaking changes. No config schema changes. No changes to
package-boost:sync itself.
UpdateCommand extends SyncCommand and overrides only the
registered name / description / hidden flag in its constructor. No
signature duplication — new options added to SyncCommand propagate
automatically.
Closes a silent-drift hole in --check that affected any consumer
on a filesystem without symlink support (Windows-without-dev-mode,
some sandboxed CI runners). Before 0.7.0, SyncCommand::linkOrCopy
fell back to a recursive file copy when [@symlink](https://github.com/symlink)() failed — and
every subsequent --check reported those copied skill directories
as unchanged regardless of whether the shipped skill's content
had drifted. CI passed, local content was stale, nobody knew.
SyncReporter::planSkillAction now has a third branch. When a
skill destination exists as a plain directory (not a symlink),
both the source tree and the destination tree are hashed
(xxh128 per file; dotfiles and dotted subdirectories skipped)
and compared. Drift is reported as an updated action with a
content: hint.
Small diffs name the affected files (cap 3):
~ .claude/skills/package-development (content: SKILL.md differs)
~ .claude/skills/package-development (content: SKILL.md differs, rules/a.md added, rules/b.md removed)
Large diffs collapse to counts:
~ .claude/skills/package-development (content: 4 differ, 1 added, 1 removed)
Hint is prefixed with content: to disambiguate from the existing
(symlink → ...) hint shape used for symlink retargets. JSON
output carries the same string in the hint field:
{ "target": ".claude/skills/package-development", "hint": "content: SKILL.md differs" }
tests/tmp/ gitignoredTest fixtures under tests/tmp/ are now ignored so an aborted
test run (Ctrl-C, fatal) can't leak artefacts into a subsequent
git add -A on a release workflow.
composer update sandermuller/package-boost
Nothing changes for consumers on filesystems with working
symlinks — the new code path is only reached when
SyncCommand::linkOrCopy's symlink attempt fails.
Consumers running --check on a copy-fallback filesystem will see
drift the first time they upgrade, surfacing any content that had
silently diverged from its shipped source. Expected; run
package-boost:sync once without --check to reconcile.
No change for JSON consumers beyond the new content:-prefixed
hint values appearing in skills.updated[].hint when content
drift is detected.
SyncReporter::hashTree(string $dir): array<string, string> —
recursive { relativePath => xxh128 } map via Symfony Finder's
->sortByName()->ignoreDotFiles(true). Skips dotfiles and
dotted subdirectories; returns [] for missing directories.
xxh128 rather than SHA/MD5 because this is change-detection,
not signing — xxh128 is ~20 GB/s vs SHA-256's ~500 MB/s and
doesn't trip phpstan-disallowed-calls.SyncReporter::renderContentHint(array $source, array $dest): string — bucket classification of added / differ / removed
files; named-list when total ≤ 3, count summary above.SyncCommandTest integration that
pre-seeds a directory destination with drifted content and
asserts --check exits 1 with the content: hint.planSkillAction still reports unchanged. Fixing it would
require linkOrCopy to force-delete the file, which risks
destroying user data when intent is unclear. Silent unchanged
remains the conservative default.hashTree(source) cache across SKILL_TARGETS — the
source tree is walked 2× per skill (once per target directory)
on copy-fallback filesystems. Measured at sub-millisecond per
walk; caching costs complexity for no real user benefit.No breaking changes. Text output carries the new (content: …)
hint format on previously-silent drift. JSON hint string is
additive — consumers already ignoring hint see no change; those
displaying it get the new prefix automatically.
Documentation-only release. Pins the --format=json schema-v1
contract ahead of the first downstream CI integration (landing in
laravel-fluent-validation v1.13) — locks the shape so future
consumers don't have to guess what fields mean or hit the same
mcp-as-array foot-gun as the pre-integration peer review caught.
Three fields that were underspecified in 0.6.0's README now have explicit semantics:
hint is advisory prose, not a command-to-run. For skills
on updated actions: "symlink → <relative target>". For
guidelines on updated / new: "+N lines" / "-N lines" /
"content updated". No hint on removed or unchanged. The
fix for any drift is always package-boost:sync without
--check — don't templatize hint as auto-fix output.line_delta (guidelines only) is scoped to the
<package-boost-guidelines> block. Since sync only rewrites that
block, it's effectively the synced-region delta; everything else
in the target file is never touched. Don't read it as a full-file
delta.mcp is a single { action, target } object, never an
array. The previous jq example in the README only drained
collection-shaped categories; it would have silently missed MCP
drift.Replaced the two-branch jq snippet with a complete one that drains
all six collection arrays (skills × { new, updated, removed },
guidelines × same) plus the MCP object branch via direct
equality on action:
jq -r '
(.skills.new, .skills.updated, .skills.removed)[]?.target,
(.guidelines.new, .guidelines.updated, .guidelines.removed)[]?.target,
if .mcp.action == "new" or .mcp.action == "updated" then .mcp.target else empty end
' | sort -u | sed "s|^| - |"
No code changes. Documentation only. Text and JSON output are byte-compatible with 0.6.0.
No breaking changes. Schema v1 contract unchanged — this release just documents what 0.6.0 already returned.
Shipped content release — driven by real feedback from two downstream
peers (laravel-fluent-validation, laravel-js-store) running
package-boost 0.3–0.4 in anger. Two new skills land, the foundation
stops assuming Pest-first, and Boost-specific commands move into
their own table so PHPUnit-only and Boost-less packages stop reading
dead copy.
resources/boost/guidelines/foundation.md and the shipped
package-development SKILL no longer imply Pest-first test running.
The php artisan test row now reads:
The package's configured test runner (
vendor/bin/pestorvendor/bin/phpunit)
Boost-specific rows (boost:install, boost:mcp) move into a
dedicated sub-table:
### Commands that require `laravel/boost`
| Instead of | Use |
|---|---|
| `php artisan boost:install` | `vendor/bin/testbench boost:install` |
| `php artisan boost:mcp` | `vendor/bin/testbench boost:mcp` |
Readers without Boost installed see the heading, understand the rows don't apply, and skip. PHPUnit-only packages stop reading Pest-first ordering as advice.
cross-version-laravel-supportPreventive workflow for packages supporting multiple Laravel / PHP majors. Activate before writing version-sensitive code. Covers:
composer.json constraints (^11.0||^12.0||^13.0 decoded)version_compare(app()->version(), ...),
method_exists feature detection, conditional trait composition--prefer-lowest /
--prefer-stableci-matrix-troubleshootingDiagnostic workflow for after a matrix cell has gone red. Covers:
roave/security-advisories), Testbench ↔ PHPUnit interlock, wrong
package floor, phpstan/larastan incompat, missing version guardcomposer why-not,
composer update ... --dry-runconflict directiveTrigger partition is deliberate: the preventive and diagnostic skills
cross-link and share only the testbench descriptor. Vocabulary like
prefer-lowest / prefer-stable lives on the diagnostic skill only,
so the skill matcher fires predictably on "CI just broke" language.
.ai/ authoring schema docsNew ## Authoring guidelines section in the shipped
package-development SKILL covers the four things downstream authors
kept reverse-engineering from sibling files:
name, description),
body is markdown, description is the trigger surface--- divider, user content
secondThe paragraph-long advice moved into cross-version-laravel-support.
Foundation now holds a pointer:
Supporting multiple Laravel / PHP majors is routine for packages. Activate
cross-version-laravel-supportbefore writing the code; activateci-matrix-troubleshootingafter a matrix cell has failed.
Single source of truth for the workflow, with a disambiguator so readers know which skill to pick without drilling in.
composer update sandermuller/package-boost
vendor/bin/testbench package-boost:sync
Or, in CI:
vendor/bin/testbench package-boost:sync --check
Downstream --check will report drift on the first run after upgrade:
CLAUDE.md, AGENTS.md,
.github/copilot-instructions.md all pick up the runner-agnostic
row, new sub-table, trimmed Cross-Version section..claude/skills/package-development/SKILL.md and
.github/skills/package-development/SKILL.md pick up the Authoring
guidelines section + the Cross-Version trim + the table reshape..claude/skills/cross-version-laravel-support/
and .claude/skills/ci-matrix-troubleshooting/ appear (plus their
.github/skills/ mirrors).Expected one-time re-sync.
SHIPPED_SKILLS constant so each new shipped
skill gets automatic coverage via a Pest dataset-driven test.
No more per-skill test-count drift.ROADMAP.md + specs/ directory in the repo as the plan
of record for 0.5.x / 0.6.x / 0.7.x work.No breaking changes. Config shape unchanged, command surface unchanged, synced file layout unchanged. Content-only evolution of the shipped skill and guideline text.
Structured output for package-boost:sync. CI scripts and release
automation can now parse drift detection results instead of regex-
matching glyph lines. Directly requested by the
laravel-fluent-validation peer after 0.4.0's --check landed;
deferred two releases while the trigger-word skills (0.5.0) shipped
first.
--format=jsonvendor/bin/testbench package-boost:sync --check --format=json
Emits a single JSON document on stdout; exit code still signals
drift (0 clean, 1 on any new/updated/removed or MCP action
other than unchanged). Warnings (Laravel Boost missing, no
sources found) are reflected in the JSON as skipped fields
rather than stderr text.
Schema v1:
{
"schema": 1,
"check": true,
"drift": false,
"skills": {
"new": [],
"updated": [],
"removed": [],
"unchanged": 24
},
"guidelines": {
"new": [],
"updated": [],
"removed": [],
"unchanged": 3
},
"mcp": {
"action": "unchanged",
"target": ".mcp.json"
}
}
Field rules:
schema — currently 1. Bumped on any breaking change to this
document.check — echoes the input flag so consumers can distinguish a
drift report from a post-write report.drift — logical-OR across categories. true if any
new/updated/removed array is non-empty, or mcp.action
is anything other than unchanged.target, optional hint
(e.g. "symlink → ../../vendor/...", "+12 lines"), and
optional line_delta (raw int for guidelines).{ action, target } — always a single target
since .mcp.json is the only MCP output.target for deterministic diffs.--show-unchanged in JSON modeBy default unchanged is an int count. Passing --show-unchanged
toggles it to an array of { target } entries, matching the
per-line text output.
When a category has no sources or a precondition fails, it reports structurally instead of via warn text:
"skills": { "skipped": "no-sources" }
"mcp": { "action": "skipped", "reason": "laravel-boost-not-installed" }
drift treats skipped categories as non-drift.
vendor/bin/testbench package-boost:sync --format=yaml
# ERROR Invalid --format value 'yaml'; expected 'text' or 'json'.
Exits non-zero with guidance.
- name: Check package-boost sync
run: |
report=$(vendor/bin/testbench package-boost:sync --check --format=json || true)
drift=$(echo "$report" | jq -r '.drift')
if [ "$drift" = "true" ]; then
echo "::error::package-boost sync drift detected"
echo "$report" | jq -r '.guidelines.updated[].target,.skills.new[].target'
exit 1
fi
Substantial refactor inside src/Console/ — zero user-visible
impact for text consumers:
SyncAction / SyncPlan — readonly value objects carrying
per-target action entries and per-category aggregates.SyncFormatter — owns both renderText (glyph + summary,
matching pre-0.6.0 byte-for-byte) and renderJson (the new
structured format).SyncWriter — filesystem apply helpers (symlink-or-copy,
guideline-block write, stale-skill removal) extracted from
SyncCommand to keep the command class focused on
orchestration.array_any() (PHP 8.4-only) usage replaced with a
collection-based drift check — was failing the CI matrix's
PHP 8.2 / 8.3 cells before the fix landed.storage/logs/ now gitignored; a test-generated laravel.log
had been accidentally committed in a prior release and
un-tracked.Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.5.0...0.6.0
Cosmetic follow-up to 0.4.0: the MCP: section of package-boost:sync
output now emits a total: ... summary line matching the Skills and
Guidelines sections.
Before (0.4.0):
Skills:
total: 24 unchanged
Guidelines:
total: 3 unchanged
MCP:
After (0.4.1):
Skills:
total: 24 unchanged
Guidelines:
total: 3 unchanged
MCP:
total: 1 unchanged
The three categories now render uniformly. --show-unchanged still
controls whether the per-target = .mcp.json line is printed
alongside the summary.
Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.4.0...0.4.1
Two major additions driven by real-world feedback from users running
package-boost against their packages: a CI drift-check mode and
per-target delta output on every sync. Also hardens .mcp.json
handling against malformed input.
--check mode for CIvendor/bin/testbench package-boost:sync --check
Computes planned actions, writes nothing, exits non-zero if any skill,
guideline, or MCP target diverges from its source. Use in CI to catch
commits where .ai/* content was edited but the generated files
(.claude/, .github/, CLAUDE.md, AGENTS.md, .mcp.json)
weren't re-synced.
Combines with the subcommand flags:
vendor/bin/testbench package-boost:sync --check --guidelines
Every sync now shows a per-target line for changes, with glyphs and a per-category summary:
Skills:
+ .claude/skills/package-development
+ .github/skills/package-development
total: 2 new, 0 unchanged
Guidelines:
~ CLAUDE.md (+12 lines)
~ AGENTS.md (+12 lines)
~ .github/copilot-instructions.md (+12 lines)
total: 3 updated
MCP:
= .mcp.json (unchanged; not listed)
Glyphs:
| glyph | meaning |
|---|---|
+ |
new — target doesn't exist yet |
~ |
updated — content or symlink target differs |
- |
removed — stale target no longer in sources |
= |
unchanged — hidden by default, counted in summary |
Skill updates annotate the new symlink target:
~ .claude/skills/package-development (symlink → ../../vendor/sandermuller/package-boost/resources/boost/skills/package-development)
Guideline updates show line-delta:
~ CLAUDE.md (+12 lines)
--show-unchanged flagvendor/bin/testbench package-boost:sync --show-unchanged
Default output is compact — unchanged targets are folded into
total: ... rather than listed per line, to avoid flooding large
guideline trees. Pass --show-unchanged to print every = entry
for debugging. Explicit flag name chosen over -v / --verbose
because Symfony already owns those for log verbosity.
.mcp.json hardeningPrevious versions assumed .mcp.json was always valid and
mcpServers was always an array. v0.4.0 survives:
null, invalid JSON, or scalar roots ("hello", 42) — treated
as empty config.mcpServers being a scalar — coerced to array before adding the
laravel-boost entry.Four regression tests guard these paths.
SyncReporter — pure functions for planning actions
(planSkillAction, planGuidelineAction, planMcpAction),
rendering glyphs, line-delta computation, and relative-path
calculation. No side effects, fully unit-testable.SyncSources — shipped-then-user directory iteration for
skills and guidelines, plus safe .mcp.json reading that handles
malformed input.SyncCommand shrunk to orchestration + IO; class cognitive
complexity stays under the project's guardrail.Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.3.4...0.4.0
Small readability polish on the sync output.
When package-boost:sync writes the <package-boost-guidelines>
block and both sources contribute content, a --- horizontal rule is
now emitted between the shipped foundation and the user's
.ai/guidelines/ content. Readers skimming CLAUDE.md / AGENTS.md
can tell at a glance where the shipped defaults end and project-
specific overrides begin.
Example:
<package-boost-guidelines>
# Package Boost Guidelines
## Foundational Context
...
## Replies
Be concise. Focus on what changed and why.
---
# Release Automation
...
</package-boost-guidelines>
No divider is emitted when only one of the two sources is present, so a repo with just the shipped foundation (or just user guidelines) still renders clean.
Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.3.3...0.3.4
Ships the package-tuned foundation and the package-development skill
reliably — independent of Laravel Boost's third-party discovery, which
doesn't work under Testbench.
SyncCommand bundles shipped resourcesvendor/bin/testbench package-boost:sync now reads
resources/boost/skills/ and resources/boost/guidelines/ from
Package Boost itself, in addition to the user's .ai/skills/ and
.ai/guidelines/. User entries still override shipped entries of the
same name.
Result:
package-development skill always lands in
.claude/skills/ and .github/skills/, even if the consuming
package has no .ai/skills/ directory.CLAUDE.md / AGENTS.md / .github/copilot-instructions.md,
rendered before any user-authored guidelines, inside the existing
<package-boost-guidelines> block.In v0.3.2, Package Boost relied on Laravel Boost's
Composer::packagesDirectoriesWithBoostGuidelines() /
packagesDirectoriesWithBoostSkills() to auto-discover our shipped
resources/boost/ content. That discovery reads
base_path('composer.json'), which under Testbench resolves to the
testbench-core skeleton — a bare Laravel install with no
require entries listing downstream packages. Result: the discovery
returns [] and every third-party package's shipped resources/boost/
content is silently invisible.
Rather than wait on an upstream Boost fix, Package Boost now bundles
its own shipped content through its own sync pipeline. This works on
every Boost version and is unaffected by whatever base_path() happens
to resolve to under Testbench.
An upstream Boost issue on the discovery bug is being filed separately so third-party guideline/skill shipping works consistently across the wider ecosystem.
composer update sandermuller/package-boost
vendor/bin/testbench package-boost:sync
After sync:
.claude/skills/package-development/SKILL.md exists (symlink or
copy).CLAUDE.md contains a # Package Boost Guidelines heading followed
by ## Foundational Context inside the <package-boost-guidelines>
block, ahead of any user-authored guidelines.If you had previously added your own .ai/skills/package-development/
skill, it will override the shipped one. If you don't want the
shipped foundation, remove it with rm -rf .claude/skills/package- development and exclude future syncs by adding a user-authored
.ai/skills/package-development/ that overrides it — or open an
issue if you'd prefer an opt-out flag.
sourceDirs() helper so skills and guidelines share a
single shipped-then-user source-list walk. Uses dirname(__DIR__, 2)
instead of a brittle ../../ path walk.afterEach cleanup so runs leave no stray
CLAUDE.md / AGENTS.md / .ai/ artifacts in the package root.Laravel Boost's Composer::packages() reads
base_path('composer.json'), which under Testbench is the
testbench-core skeleton. Third-party package resources/boost/
content is therefore undiscoverable through Boost's own pipeline.
Package Boost works around this by shipping through its own
SyncCommand. The upstream fix — either walking up for a
non-skeleton composer.json or exposing a rootComposerJsonPath()
hook — is being pursued separately.
Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.3.2...0.3.3
Two linked fixes so Laravel Boost's Foundation section actually disappears when you ask for it, and a package-tuned replacement takes its place.
Ships resources/boost/guidelines/foundation.md. Frames the codebase
as a Laravel package, not an application: Testbench harness,
semver discipline, public-API surface, cross-version compatibility,
vendor/bin/testbench vs php artisan. Boost auto-discovers
third-party guidelines via
Composer::packagesDirectoriesWithBoostGuidelines(), so any package
that installs sandermuller/package-boost gets the package-tuned
foundation for free inside Boost's <laravel-boost-guidelines> block.
'foundation' is added to the default
excluded_boost_guidelines list, so Boost's app-tuned foundation is
stripped — our version replaces it rather than duplicating it.
In v0.3.1, publishing the config wrote to
workbench/config/package-boost.php (correct location). But Testbench
only loads workbench/config/ when
workbench.discovers.config: true is set in testbench.yaml — which
most packages don't. The result: users edited their workbench config,
nothing changed, and a stale vendor-skeleton publish from v0.3.0
silently won.
The service provider now reads
workbench_path('config/package-boost.php') itself during register()
and does a top-level array_replace over the shipped defaults. User
overrides win regardless of testbench.yaml, over both the shipped
defaults and any stale vendor/orchestra/testbench-core/laravel/config/
leftover. Keys your workbench file doesn't set still fall back to
shipped defaults.
Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.3.1...0.3.2
Patch release fixing the publish destination introduced in v0.3.0 and hardening the Laravel Boost integration.
Publish to workbench/config/, not Testbench's vendor skeleton.
In v0.3.0, vendor/bin/testbench vendor:publish --tag=package-boost-config
wrote into vendor/orchestra/testbench-core/laravel/config/package-boost.php
(shown as [@laravel](https://github.com/laravel)/config/... in CLI output). That location works
until composer rewrites vendor/. The config is now published to
workbench/config/package-boost.php in your package repo, which
Testbench's LoadConfigurationWithWorkbench bootstrap loads
automatically.
class_exists no longer triggers Composer autoload on every boot.
The Boost-installed probe now passes false to skip the autoload
chain when Laravel Boost is not installed. Safe because Laravel
registers all providers before booting any, so if Boost is present
its class is already loaded by the time we check.
boostIsInstalled() seam) and the publish-destination path.PackageBoostServiceProvider::PUBLISH_TAG constant so
tests and publishers reference the same value.static closure on
SyncCommandTest::beforeEach that broke Pest's $this binding.Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.3.0...0.3.1
Laravel Boost ships guidelines aimed at application development — Inertia,
Livewire, Filament, deployments, Herd/Sail, and so on. In a package
repository these sections just add noise to CLAUDE.md / AGENTS.md.
This release wires Package Boost into Boost's existing
boost.guidelines.exclude config so app-only guidance is dropped during
guideline composition, without manual edits.
config/package-boost.php — new publishable config with
excluded_boost_guidelines, merged into boost.guidelines.exclude at
service provider boot.deployments, herd,
sail, laravel/style, laravel/api, laravel/localization,
inertia-*, livewire/*, volt/*, fluxui-*, folio/*,
pennant/*, wayfinder/*, filament/*, nightwatch/*, pulse/*,
tailwindcss/*, vite/core.Laravel\Boost\BoostServiceProvider existing, so nothing leaks into
foreign config.publishes() only runs in console, avoiding wasted work on HTTP
boots.To customise the exclusion list:
vendor/bin/testbench vendor:publish --tag=package-boost-config
Edit config/package-boost.php and add or remove keys. Keys match
Boost's GuidelineComposer keys exactly (e.g. livewire/core,
filament/v4, herd).
SyncCommandTest beforeEach now wipes .ai/ and CLAUDE.md,
fixing pre-existing flakiness in the "warns when no … directory
exists" tests.Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.2.0...0.3.0
Skills are now synced using symlinks instead of copying directories. This keeps agent skill directories (.claude/skills, .github/skills) in sync with your source .ai/skills without duplicating files. Falls back to copying on filesystems that don't support symlinks.
Full Changelog: https://github.com/SanderMuller/package-boost/compare/0.1.0...0.2.0
Initial release 🎉
Full Changelog: https://github.com/SanderMuller/package-boost/commits/0.1.0
How can I help you explore Laravel packages today?