Releases: SanderMuller/project-boost-laravel
Release list
v1.3.0
project-boost:sync now retires laravel/boost's boost.json once it has taken over what that file describes — the step that stops herd link from silently re-seeding guidance behind this package. Additive; no migration.
Action required: this release requires sandermuller/boost-core ^1.6 (up from ^1.0).
composer require --dev "sandermuller/project-boost-laravel:^1.3" -WAdded
project-boost:syncretireslaravel/boost'sboost.jsonafter a successful sync. The file is laravel/boost's install state, read byboost:installandboost:updateand nothing else — not the MCP server (boost:mcpstarts a server the ServiceProvider registers unconditionally), not this package (it re-derives fromvendor/laravel/boost/.ai/every run), not boost-core. Retiring it is what stops the automatic re-seed:boost:updatebails out when the file is missing, andherd linkrunsphp artisan boost:updateon its own whenevervendor/laravel/boostis present (Herd's bundled valet CLI), which otherwise rewrites the guidance files inside laravel/boost's marker and reinstalls its skill directories behind this command's back.- Adopt before retire. The file's
agentslist is the only record of what was picked in laravel/boost's installer, and nothing imports it automatically. While it names an agent the project's own config does not declare, the file stays put and the sync says which agent and how to adopt it —vendor/bin/boost installpre-selects exactly that set. An agent boost-core has no case for (antigravity,factory,grok_build,pi,zed) can never be adopted, so it never blocks; the sync names it instead, because nothing this package emits reaches that agent and retiring the file ends laravel/boost's updates for it too. - Archived, not deleted. The file moves to
.boost/boost.json.retired(or.config/boost/boost.json.retiredwhen the project uses that config layout — the layout decides, not whichever directory happens to exist). Both directories are gitignored by boost-core and skipped by its stale-file sweep, so the archive survives later syncs and never dirties the working tree. An existing archive is never overwritten: identical content means the source is simply dropped, and different content is archived alongside under a content-addressed name. Restore from there, or runphp artisan boost:installto regenerate. - Only on a real takeover. A sync that injected no laravel/boost skills or guidelines (laravel/boost export-ignores its
.aipayload, so a prefer-dist install has none), or that skipped a guidance path because it is a live symlink, has taken over nothing — neither case is an error, so the sync still exits0, and in both the file is kept with the reason printed. - Further guards: with gitignore management off there is no state directory to archive into, so the file is kept rather than parked somewhere untracked; a symlink anywhere on the destination path is refused (
rename()would follow it out of the project); an archive name already taken by different content is refused rather than overwritten; aboost.jsonrecording no agent list is not laravel/boost's live install state — another tool's file, or oneboost:updatealready refuses to act on — and is kept; a failed sync keeps the file, since laravel/boost's own path stays the fallback; a failed archive leaves the original in place. --dry-runreportswould-archiveand moves nothing.--keep-boost-jsonopts out entirely.
- Adopt before retire. The file's
Changed
- Requires
sandermuller/boost-core ^1.6(was^1.0). Two behaviours the retirement flow is documented against landed in1.6.0: the stale-file sweep is manifest-gated, so the.boost/boost.json.retiredarchive survives later syncs instead of being reaped as unowned; andboost installpre-selects the agents recorded in laravel/boost'sboost.json, which is the adoption step this command waits for before retiring the file.
--keep-boost-json joins the frozen CLI surface in PUBLIC_API.md from this release. Validated across the CI matrix (PHP 8.3/8.4 × Laravel 12/13, prefer-lowest and prefer-stable).
Full Changelog: 1.2.0...1.3.0
v1.2.0
Restores compatibility with laravel/roster 1.0.0, whose API rewrite made every composer update on a consumer app hard-crash during the project-boost:sync post-hook.
Action required: this release requires laravel/boost ^2.5 (up from ^2.4). See UPGRADING.md.
composer require --dev "sandermuller/project-boost-laravel:^1.2" -W-W matters: laravel/boost is usually a sibling top-level require in the consuming app, so it has to move to ^2.5 in the same resolve.
Fixed
-
composer update/installno longer fatals withClass "Laravel\Roster\Enums\Packages" not found.laravel/roster 1.0.0(2026-07-18) removed thePackagesenum and theRosterclass this package was built against. The failure was unrecoverable rather than degrading to the intended permissive fallback:LaravelBoostGuidelineGate::EXCLUDED_PACKAGESreferenced enum cases in a class-constant initializer, which PHP evaluates on class initialization — sopermissive(), the graceful-fallback path itself, threw before anyclass_exists()guard could run. Package identity is now a plain composer/npm name string throughout, andRoster::scan()becomesProjectScan::scan(). -
Guideline dirs resolve through
laravel/boost's own name mapper. Package name → guideline dir now delegates toPackageRegistry::guidelineName()instead of slugifying locally. Pre-1.0 Roster'sPackage::name()returned the enum case name (FLUXUI_PRO), which the old slugify handled correctly; Roster 1.0 returns the composer name (livewire/flux-pro), which it would not have. Left unfixed, this would have replaced the fatal with silence — every package guideline suppressed, no error. -
npm-ecosystem packages are gated again. Discovery scans both ecosystems (
php()+js()), matchinglaravel/boost's ownDiscoverPackagePaths::packages(). Theinertia-react,inertia-svelte,inertia-vueandtailwindcssguideline dirs are npm-driven and had no gate signal from a php-only scan.
Changed
-
Requires
laravel/boost ^2.5(was^2.4).Laravel\Boost\Support\PackageRegistry— which this package now mirrors for package constants and the name → dir map — landed in2.5.0alongside boost's own Roster 1.0 adaptation.2.4.xstill requireslaravel/roster ^0.5, so there is no version of this fix that works on the2.4line. -
laravel/roster ^1.0is now an explicit requirement. It was previously pulled in only transitively throughlaravel/boost, despite this package type-hinting its classes directly — which is how a major upstream rewrite reached consumers with no constraint to stop it. -
The known-package universe is derived from the dirs
laravel/boostships under.ai/, rather than enumerated from a hardcoded list. Roster 1.0 removed the enum that supplied it, and boost's replacement keeps its name → dir map private. Scanning is also self-maintaining: a guideline dir boost adds in a future release is gated correctly without a release here. Verified equivalent against boost 2.5 — every package dir it ships was aPackagescase, and the enum cases with no shipped dir were already no-ops.
Internal
-
LaravelBoostGuidelineGate::fromRoster()becamefromProjectScan()and takes aProjectScan;VersionResolvertakes a?ProjectScan. Both are@internal— thePUBLIC_API.mdsurface (CLI commands, options, exit codes, config keys) is unchanged. -
Added a regression guard asserting the gate's exclusion and must-be-direct lists still match
laravel/boost's own. The gate is a 1:1 mirror ofDiscoverPackagePaths, and unlike a removed class, a changed policy array drifts silently — no fatal, just guidelines quietly emitted or suppressed. -
Dropped the abandoned
rector/type-perfectdev dependency, superseded bytomasvotruba/type-coverage, which now bundles it. Both installed made PHPStan abort during container compilation on a duplicate service registration, exiting non-zero with no output — so the quality gate looked green while analysing nothing.
Full Changelog: 1.1.0...1.2.0
v1.1.0
Adds project-boost:reconcile — a guided takeover that captures laravel/boost-seeded agent guidance before a sync would overwrite it. Additive; no migration.
Added
project-boost:reconcile— a diff-first guided takeover for thelaravel/boostcoexistence seam.boost:installseeds its guidelines directly into your agent files (CLAUDE.md/AGENTS.md/GEMINI.md/ …) inside a<laravel-boost-guidelines>marker; a markerless boost-core sync regenerates those files wholesale, so hand-authored content outside the marker is lost.reconciledetects foreign-seeded files by that marker (the same signalvendor/bin/boost doctoruses), backs each up verbatim to.boost-reconcile/, captures the hand-authored residual into.ai/guidelines/reconciled.md(append-with-dedup — it never clobbers edits you have made there), then runsproject-boost:syncso the captured content is re-composed into every agent file. Options:--dry-run,--force,--no-sync.project-boost:syncnow warns when a sync would overwritelaravel/boost-seeded guidance, pointing atproject-boost:reconcilefirst (warn-and-continue, matching boost-core's default).- A post-
boost:installnudge towardproject-boost:reconcile, fired only after a bareboost:installthat actually seeds guidance — silent afterboost:install --mcp(which keeps laravel/boost's writers dormant, so nothing is seeded) and whileproject-boost:installis driving its own sequence.
Docs
- New
docs/laravel-coexistence.md: the canonical command sequence (boost:installonce →project-boost:reconcileonce →project-boost:syncongoing), the division of labor withlaravel/boost, and why a barevendor/bin/boost syncon a wrapper project loses content. The README andPUBLIC_API.md(which addsproject-boost:reconcileto the frozen CLI surface) link to it.
The new command is part of the 1.x @api/CLI surface from this release. Requires boost-core ^1.0; validated across the CI matrix.
Full Changelog: 1.0.0...1.1.0
v1.0.0
The stability commitment. project-boost-laravel joins the boost family's 1.0 line — the last freeze in dependency order (boost-core → package-boost-php → package-boost-laravel → here). No runtime change: 1.0.0 is the 0.10.2 surface, re-validated against the boost-core 1.x family and frozen.
The 1.0 contract
This package is a behavior wrapper, not a class library — its contract is the CLI, config, and sync behavior, documented in PUBLIC_API.md. From 1.0.0 that surface is locked for the 1.x line (no break in a MINOR or PATCH):
- CLI —
project-boost:install/:sync/:where, their documented options, and the exit-code contract. - Config —
config/project-boost-laravel.phpkeys (suppress_upstream_writers,laravel_boost_ai_root). - Discovery & frozen formats — the service-provider FQCN, the
BoostWrappercontract, the laravel/boost tag manifest, and the install-gated guideline behavior. - No
@apiPHP classes — nothing here is constructed or extended by name; theSkillRendererimplementation stays@internal.
Changed
- Requires
sandermuller/boost-core ^1.0(was^0.23.0||^1.0) — the0.23range is dropped. boost-core1.0is a drop-in over0.23.3(no API break), so a consumer already on^0.23bumps mechanically. If you requiresandermuller/boost-coreorsandermuller/boost-skillsdirectly, move them to the1.0family in the samecomposer update -W(boost-skills2.2.0already admits boost-core^1.0). SeeUPGRADING.md.
Re-validated against the resolved boost-core 1.x family across the CI matrix (prefer-lowest → 1.0.0, prefer-stable → 1.1.0): full suite green, the boost-core @api-closure guard intact, static analysis clean, sync no-drift.
Full Changelog: 0.10.2...1.0.0
v0.10.2
Changed
-
Now allows
sandermuller/boost-core ^1.0(was^0.23.0). boost-core 1.0.0 is a drop-in over 0.23.3 (no API break), so the runtime requirement widens to^0.23.0||^1.0. This unblocks wrapper consumers from resolving boost-core 1.0 transitively —project-boost-laravelwas the last consumer-facing cap in the cascade (boost-skillsalready admits^1.0). Validated against the 0.23.3 surface, which is byte-identical to 1.0.0: full suite green (65/65), the boost-core@api-closure guard intact, PHPStan clean.Consumers that require
sandermuller/boost-coredirectly can bump it to^1.0in the samecomposer update -W. No action needed otherwise — a scoped update under an existing^0.10constraint picks this up.
Full Changelog: 0.10.1...0.10.2
v0.10.1
Fixed
project-boost:wherenow install-gates guidelines exactly likeproject-boost:sync.wherebuilt its guideline reader WITHOUT the install-gatesyncapplies, so it over-reported laravel/boost guidelines (inertia / livewire / pest / sail …) for packages the host hasn't installed — misleading for a command whose whole job is "what ships for this project shape." Both commands now share the gate through a singleGatesGuidelinesconcern, sowherereports the setsyncactually emits.- The
laravel_boost_ai_rootconfig override is now honored. The key shipped inconfig/project-boost-laravel.php— documented as a test / non-standard-vendor-layout override — was never read; both commands hard-codedvendor/laravel/boost/.ai.project-boost:syncandproject-boost:wherenow resolve the asset root through it (falling back to the standard vendor path), making the documented override actually work.
Docs
UPGRADING.md: corrected the 0.10 upgrade command, sourced from consumer adoption feedback. The prior command was incomplete on three counts: it omitted--dev(a barecomposer requiresilently promotes this dev package into productionrequire); consumers that requiresandermuller/boost-skillsdirectly must co-bump it to^2.1 -Win the same command (a directly-requiredboost-skills ^2.0.6transitively pinsboost-core ^0.22, colliding with 0.10's^0.23floor, and-Walone can't resolve a sibling top-level require); and each boost package should be pinned to the floor you validated against, not the wrapper's transitive floor, to avoid generated-guidance drift in repos that auto-runproject-boost:syncfrom composer scripts.
Internal
- The
project-boost:whereguideline-gate regression test is now hermetic: it points thelaravel_boost_ai_rootoverride at a small fixture.aitree instead of the realvendor/laravel/boost/.aipayload — which laravel/boost export-ignores, so it is absent from a prefer-dist Composer install (and was failing the CI test matrix while passing locally). - Removed the orphaned
VersionResolver::withHostRoster()static factory, left dead by thewheregate refactor.
Full Changelog: 0.10.0...0.10.1
v0.10.0
Adopts the boost-core 0.23 line and locks the package's @api/@internal surface ahead of 1.0.
Breaking
-
Requires
sandermuller/boost-core ^0.23(was^0.22), alongsidesandermuller/package-boost-laravel ^0.15andsandermuller/boost-skills ^2.1. Most apps only bump this package — boost-core resolves transitively:composer require "sandermuller/project-boost-laravel:^0.10"If you require any boost package directly, move them to the
0.23line together. Running againstboost-core < 0.23no longer resolves.
Fixed
- A malformed
metadata.boost-tagsnow fails closed. laravel/boost skills are tag-filtered against yourwithTags(). Previously a malformed (non-string)boost-tagsvalue was treated as "untagged" and shipped to every agent — the opposite of the engine's fail-closed contract. The package now tokenizes and validates tags through boost-core's canonicalBoostTags, so a malformed value ships nowhere, matchingboost's own behaviour. (An explicitly-emptyboost-tagsis untagged on purpose — no sidecar-manifest fallback.)
Internal
- Locked the
@api/@internalsurface for1.0, documented in the newPUBLIC_API.md. This package exposes no@apiPHP classes — it's an artisan/CLI-driven wrapper, so its semver-protected contract is its CLI commands, config keys, and documented behaviour, not a class API. An architecture test fails if anysrc/class is added without an explicit@api/@internalmark.
Full Changelog: 0.9.1...0.10.0
v0.9.1
A patch hardening the upgrade path, from real-world adoption feedback across several consumer apps.
Fixed
- A pre-0.20 variadic
withTags(...)in your boost config no longer abortscomposer update. boost-core0.20madewithTags(Tag::Php, ...)intowithTags([...]), and theproject-boost:synccomposer hookrequires your config — so an un-migrated variadic call threw aTypeErrorthat aborted the whole update with a raw stack trace.project-boost:sync,project-boost:where, andproject-boost:installnow catch a config-load failure and print a clean, actionable migration hint with a non-zero exit instead. (A missing config likewise gets a friendly "create one" hint rather than an uncaught exception.) - The README minimal example used the pre-0.20 variadic
withTags(Tag::Laravel, Tag::Php)— which wouldTypeErrorif copy-pasted underboost-core ^0.22. Corrected to the array form.
Internal
- Added an
@api-closure conformance test: it scans every boost-core import undersrc/and asserts each symbol is part of boost-core's frozen@apisurface, so the package can only depend on the 1.0-stable contract.
Docs
UPGRADING.md: hand-editwithTags(...)→withTags([...])before bumping (the post-update sync hook loads your config before any auto-migration can run).README.md: clarified that boost-core config resolves from.config/boost.phpor a legacy rootboost.php, while laravel/boost'sboost.jsonstays at the project root (laravel/boost owns it).
Full Changelog: 0.9.0...0.9.1
v0.9.0
Adopts the boost-core 0.22 line and moves every sync-driving and wrapper code path onto boost-core's frozen @api surface, ahead of boost-core's 1.0 freeze. Also fixes the console commands for the canonical .config/boost.php config layout.
Breaking
-
Requires
boost-core ^0.22(was^0.16). The boost stack moves together —sandermuller/package-boost-laravel ^0.14(the Laravel umbrella that ships theemit(): iterableFileEmittercontract and floorspackage-boost-php ^0.18.1) andsandermuller/boost-skills ^2.0.6. Running againstboost-core < 0.22no longer resolves.Most apps only need to bump this package — boost-core comes through transitively:
composer require "sandermuller/project-boost-laravel:^0.9"If you pin any boost package directly, move them to the
0.22line together:sandermuller/boost-core ^0.22,sandermuller/package-boost-laravel ^0.14,sandermuller/boost-skills ^2.0.6.
Fixed
project-boost:sync,project-boost:sync --dry-run,project-boost:where, and the non-interactiveproject-boost:installnow resolve the boost config from both the legacy rootboost.phpand the canonical.config/boost.phplayout (boost-core ≥ 0.17). They previously hard-coded a rootboost.phpcheck and aborted withNo boost.php foundfor projects already on the.config/layout, before the sync ever ran.
Internal
- Re-pointed the package onto boost-core's
@apisurface: sync now drives theBoostSyncfacade, agent targets resolve viaAgent::target(), config reads viaBoostConfig::load(), skill emit paths viaAgentTarget::skillRelativePathForName(), and frontmatter via the now-@apiFrontmatterParser. Every boost-core symbol the package imports is@api, so it stays stable under boost-core's1.0semver guarantee — no longer relying on engine internals.
Full Changelog: 0.8.2...0.9.0
v0.8.2
0.8.2
Makes guideline and skill discovery order deterministic across operating systems, eliminating content-free sync churn between macOS and Linux. Patch release — no constraint or API changes.
Fixed
Deterministic guideline/skill ordering across OSes
LaravelBoostGuidelineReader and LaravelBoostAssetReader walked vendor/laravel/boost/.ai/ with a Symfony Finder that had no sortByName(), so each yielded entries in filesystem-iteration order — APFS hash order on macOS, ext4 readdir order on Linux. The guideline reader's output is appended in that order and boost-core's SyncEngine concatenates guidelines into CLAUDE.md / AGENTS.md / GEMINI.md in array order.
The result: the same commit regenerated those agent files with a different section order depending on which OS ran the sync. Downstream that showed up as a large, content-free reorder diff (~157 lines, zero content change) and — worse — a CI auto-fix loop, where CI on Linux kept rewriting the order a developer had committed on macOS and pushing the "fix" back.
Both finders now call ->sortByName(), pinning a stable lexicographic order regardless of the underlying filesystem. A reader test asserts the native emission order is already lexicographic (it fails without the sort).
If you previously worked around this by gating sync off in CI, you can drop that guard once on 0.8.2 — with deterministic ordering, regenerating in CI is byte-stable against a local sync of the same commit.
Full Changelog: 0.8.1...0.8.2