mike-bronner / laravel-development-settings
Shared settings for Laravel development.
Package info
github.com/mike-bronner/laravel-development-settings
Type:composer-plugin
pkg:composer/mike-bronner/laravel-development-settings
Requires
- php: ^8.3
- composer-plugin-api: ^2.0
- illuminate/collections: ^11.45.3|^12.41.1|^13.0
- illuminate/support: ^11.45.3|^12.41.1|^13.0
- larastan/larastan: ^3.5
- laravel/boost: ^2.9
- laravel/pint: ^1.24
- laravel/prompts: ^0.3
- mike-bronner/clean-code: ^0.2.1
Requires (Dev)
- composer/composer: ^2.0
- mockery/mockery: ^1.6
- nunomaduro/collision: ^8.6
- pestphp/pest: ^3.0
Suggests
None
Provides
None
Conflicts
- laravel/boost: <2.9
Replaces
- mikebronner/development-settings: 0.5.5
- dev-main
- 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.18
- 0.1.17
- 0.1.16
- 0.1.15
- 0.1.14
- 0.1.13
- 0.1.12
- 0.1.11
- 0.1.10
- 0.1.9
- 0.1.8
- 0.1.7
- 0.1.6
- 0.1.5
- 0.1.4
- 0.1.3
- 0.1.2
- 0.1.1
- 0.1.0
- dev-contribute/crossbible-20260929151606
- dev-sync/mike-bronner/laravel-development-settings
- dev-chore/pr-triage-followups
- dev-feature/boost-resources-guidelines
- dev-contribute/crossbible-20260912151314
- dev-contribute/extract-greek-lemmas-from-lexicon-20260912150832
- dev-contribute/crossbible-20260818231657
- dev-contribute/crossbible-20260818231430
- dev-contribute/crossbible-20260818231035
- dev-contribute/crossbible-20260818230926
- dev-contribute/crossbible-20260818230652
- dev-sync/mike-bronner/the-index
- dev-sync/mike-bronner/laravel-model-caching
- dev-sync/mike-bronner/calvinball
- dev-fix/relocate-eloquent-guidance-to-laravel-skill
- dev-feature/manifest-sync-and-boost-packages
- dev-sync/CrossBibleInc/data-importer
- dev-sync/CrossBibleInc/crossbible-laravel-vapor
This package is auto-updated.
Last update: 2026-10-03 03:14:53 UTC
README
Shared developer settings, tooling configuration, and AI guidelines for Insight repositories.
๐ฆ Installation
composer require mike-bronner/laravel-development-settings
That's it. The package automatically syncs files on every composer install and composer update.
It also brings the shared tooling with it, as its own Composer requirements: Laravel Boost, Laravel Pint, Larastan and mike-bronner/clean-code. Composer installs them with the package, so you do not require them yourself. The plugin never edits your composer.json and never runs Composer. A project that an earlier release gave these packages as dev dependencies keeps them: nothing is removed.
The one exception is mike-bronner/clean-code, if you want its guidelines in your agent files. It ships them as Laravel Boost guidelines, and Boost composes the guidelines of a direct dependency only. Require it yourself:
composer require --dev mike-bronner/clean-code
Until your composer.json requires it (in require or require-dev), every composer install and composer update prints that command. Once it does, the plugin adds mike-bronner/clean-code to the packages list in boost.json, next to this package (see "How the AI guidelines and skills reach your project" below).
Orchestra Testbench is not among them. Releases 0.3.4 and 0.4.0 required it, so it reached every application, and there it broke pest --parallel: Pest skips its Laravel handler whenever Testbench's TestCase class exists, so every worker shares one database. Composer removes it from an application on the next update. A package requires Testbench itself (see "Packages" below).
If your application's own composer.json lists orchestra/testbench, pest --parallel stays broken for as long as Testbench is installed. Remove it with composer remove --dev orchestra/testbench, unless your application needs it for its own reasons.
๐ง How It Works
This package is a Composer plugin that hooks into Composer's pre-update, post-install and post-update events. Before an update, it offers to contribute any edits you made to its installed guidelines and skills (see "Contributing edits made in vendor" below). After an install or update, it:
- Syncs tracked config files (
pint.json,phpcs.xml, โฆ) from the package into your project - Registers itself with Laravel Boost by adding its name to the
packageslist in yourboost.json(wherever Boost can run: an application, or a package with Orchestra Testbench installed). It addsmike-bronner/clean-codetoo, when yourcomposer.jsonrequires it, and otherwise tells you to require it. In a package it also writes anartisanshim and a managed.gitattributes(see "Packages" below) - Preserves local modifications โ changed files aren't overwritten, and removed-upstream files you customized aren't deleted without asking
- Composes Laravel Boost โ an interactive
composerrun on a terminal runsphp artisan boost:installon that terminal: Boost's own prompts choose the features, packages and agents, you see its output, and Boost saves your agents toboost.json. Any other run, CI and--no-interactionincluded, runsphp artisan boost:install --no-interactionwith--guidelines --skills --mcp, whatever yourboost.jsonsays, and shows only a one-line result; packages run the same command through theartisanshim (see "Packages" below). Composition is refused, by file and with the reason, when it would overwrite hand-written content (see "Why a run can refuse to compose" below). A run that composes nothing is reported as failed (see "Choosing your agents" below) - Removes the legacy
.aisymlink and.dev-settings-boostfile left by releases before the move toresources/boost(see "Upgrading" below)
How the AI guidelines and skills reach your project
The shared guidelines and skills ship at resources/boost/guidelines/ and resources/boost/skills/, which is Laravel Boost's own convention for a package. Boost finds them in vendor and composes them into your agent files (CLAUDE.md, .claude/skills/, โฆ) alongside its own. Nothing is copied or symlinked into your project.
Two conditions have to hold, and both are enforced:
- Boost only composes a package it is told about. With no entry under
packagesinboost.json, Boost discovers the directories and then filters every one of them back out โ zero guidelines, silently. Boost cannot add the entry itself during a Composer run, so the plugin writes it. - Boost 2.9 or newer. Earlier releases keyed third-party guidelines by package name inside their per-file loop, so only the last of the shipped guideline files survived. This package declares a Composer conflict with
laravel/boostbelow 2.9, so Composer refuses the combination instead of installing it. A project locked to an older Boost has to update it alongside this package:composer update mike-bronner/laravel-development-settings laravel/boost.
Your project is a direct dependency's consumer or it gets nothing: Boost excludes transitive dependencies by design, so a package that picks this one up indirectly receives no guidelines.
The same rule applies to mike-bronner/clean-code, which this package requires. Installed only through this package, it is transitive, so Boost composes none of its guidelines. Require it directly, and the plugin lists it in boost.json on the next run. The plugin adds it only when it is direct, because Boost ignores a listed package that is not.
Boost's PHP guideline
Boost's own php/core guideline tells agents to prefer PHPDoc blocks and to use array shape types in them. The shipped laravel skill says the opposite: no comments or docblocks unless asked. So this package replaces it. Its Laravel service provider, discovered automatically, adds php to boost.guidelines.exclude, and keeps any guidelines your app already excludes there. The shipped 05-php guideline then carries every other rule of Boost's php/core unchanged, plus the no-docblocks rule.
Nothing is written to your .ai directory or to config/boost.php. If you turn off package discovery for this package (extra.laravel.dont-discover), Boost composes its own php/core again. The plugin then reports the Boost run as failed, names the agent file, and says why.
Packages
A package has no artisan of its own. This package does not install Orchestra Testbench, so require it yourself: composer require --dev orchestra/testbench. When Testbench is installed (vendor/bin/testbench), the plugin writes an artisan: a short shim that boots Testbench rooted at your repository. From then on the package runs Boost the way an app does. composer update runs php artisan boost:install, Boost writes boost.json, the skills, the agent files and php artisan boost:mcp MCP entries into your repository, and every MCP tool works, record-rule included. MCP entries an earlier release pointed at vendor/bin/testbench are rewritten by the same run. Choose your agents with php artisan boost:install, as in an app.
Commit the shim. Its first comment line marks it as this package's file: an artisan without that line is an app's and is never touched. The plugin updates a shim it shipped before and keeps one you edited, listing it as locally modified. The shim creates bootstrap/cache and storage/framework/views on each run, because Testbench cannot boot rooted without them. The shipped .gitignore ignores both. Without Testbench installed, the shim stops with an error that names the missing dependency.
The shim must not reach the people who install your package, so the plugin also manages a .gitattributes that marks /artisan as export-ignore, keeping it out of the Composer dist archive. It works like the managed .gitignore: the plugin owns the lines above the sync marker, and your own rules go below it. An existing .gitattributes has no marker yet, so an interactive composer update offers to add it and moves your whole file below it. A non-interactive run only warns, and the shim stays in your archive until you accept.
php artisan test runs your suite rooted at the repository, like every Artisan command. vendor/bin/phpunit is unaffected.
A package without vendor/bin/testbench (it does not require Testbench, its install is incomplete, or it uses a custom Composer bin-dir) gets no shim and is not composed. The run says so, and names the command that installs Testbench.
Choosing your agents
boost.json is gitignored, so a fresh clone has none. The plugin runs boost:install rather than boost:update for that reason: install writes the config it needs, where update finds guidelines and skills disabled and composes nothing.
Run non-interactively, boost:install composes for the agents boost.json names. When it names none, Boost picks the agents it detects on the machine (an agent's CLI on the PATH, its app installed) and in the project (its config directory or guideline file). It does not record that pick, so a non-interactive run warns every time until you choose. An interactive composer install or composer update on a terminal asks you and saves the answer. So does running Boost yourself:
php artisan boost:install
An interactive run also shows Boost's list of third-party packages. If you untick this package there, Boost composes none of its guidelines or skills for that run, and the next composer run adds it back to boost.json.
When Boost detects no agent at all, it exits successfully having written nothing. The plugin checks for a freshly composed agent file after the run, and reports the run as failed when there is none, rather than printing "done".
In an application, Boost also registers its commands only when APP_ENV is local or APP_DEBUG is true. On a clone with no .env yet, the run fails, and the next composer install after you create one composes.
Why a run can refuse to compose
Boost writes its composed guidelines by replacing the region between an opening and a closing marker tag. The pattern is non-greedy and it anchors on the first opening tag anywhere in the file. So a hand-written section that names the opening tag in prose becomes the start of the match, the real block's closing tag becomes its end, and everything in between is replaced by generated content. This is a defect in Boost, not in your file. It has already cut one project's agent file from 299 lines to 125.
This package composes unattended, on every install and update, at a moment you did not choose. So it reads your markdown files first and stops before composing when one of them would be damaged:
- Two or more opening tags. The next composition replaces everything from the first tag to the nearest closing tag after it.
- One opening tag with no closing tag after it. This run would compose cleanly and append a real block, which leaves two opening tags behind. The run looks successful and arms the next one, so it is refused now.
The run names the file and the reason, and changes nothing. Fix the file yourself โ remove or rephrase the prose mention, or close the tag โ then run Composer again. The package will not repair it for you: where your own writing ends cannot be read from the file, and guessing wrong destroys the content the check exists to save.
Your project's own guidelines
.ai/ at your project root belongs to your project, and this package never writes there. Boost composes .ai/guidelines/*.md into the same generated block as the baseline arriving from vendor, so a project adds its own guidance simply by dropping a file in. .ai/skills/ and .ai/rules/ work the same way. Commit all of it โ the shipped .gitignore deliberately does not ignore .ai.
Upgrading from the symlink releases
Releases before this one delivered .ai as a symlink into vendor/mikebronner/development-settings. The first composer install or composer update on the new version removes that link, and reports it as - .ai (stale symlink into vendor). Left in place the link would dangle, because the directory it points at is gone โ and while it stands, .ai is a window into vendor that your project cannot write to.
A real .ai directory is never touched. Only a symlink resolving inside this package, or inside the vendor path it used under its old name, is removed.
The same run removes .dev-settings-boost, the fingerprint cache the old runner wrote into every project root, and reports it as - .dev-settings-boost (stale Boost fingerprint). Only a file holding a bare fingerprint is removed. The old package runner's bootstrap/cache, storage/framework and storage/logs directories are not removed, because they may hold your own files. The shipped .gitignore keeps ignoring them.
Upgrading from mikebronner/development-settings
The package was renamed from mikebronner/development-settings to mike-bronner/laravel-development-settings when its repository moved. Composer treats the two names as different packages, so the old requirement keeps installing the old releases until you swap it:
composer remove mikebronner/development-settings composer require mike-bronner/laravel-development-settings
If your composer.json names the old repository under repositories, point it at https://github.com/mike-bronner/laravel-development-settings first.
The new package declares that it replaces mikebronner/development-settings. So if you add the new requirement and forget to remove the old one, Composer installs only the new package, and the two plugins never run side by side. Remove the old requirement anyway: a requirement on a name that nothing ships under is only confusing.
The first run under the new name cleans up after the old one. It replaces mikebronner/development-settings with the new name in the packages list of boost.json, and leaves every other entry alone. It also removes a symlink-era .ai link into vendor/mikebronner/development-settings, which dangles once Composer deletes that directory. A consumer's copy of .github/workflows/sync-developer-settings.yml is updated to call the reusable workflow at its new path, unless you modified it locally.
When a run fails
A failure the plugin reports itself, such as a file it could not write or a failed Boost run, is listed in the summary, and the run carries on. An exception or error thrown while the plugin publishes stops only the plugin, not Composer. The plugin prints the cause (the error's class, message, file and line) and tells you to run the same command again to finish setup: composer install after an install, composer update after an update. Composer then runs your project's own post-install-cmd or post-update-cmd scripts, which an uncaught error would skip. Add -v to see the trace.
One known cause is an update that also updates this plugin. It was observed on an update straight from mikebronner/development-settings 0.2.0 to 0.5.1. Composer loads the new plugin during the run, but classes the old version had already loaded stay in memory, and the new code can call a method they lack. The next run starts fresh. If the same failure comes back on that run, it is a bug. Please report it with the printed cause.
When the CI environment variable holds any non-empty value, as it does on most CI systems, such a failure still fails the run. false and 0 count as set too. An empty CI counts as unset. A CI install comes from the lock file and updates nothing mid-run, so a failure there is a real bug and must not pass unnoticed.
Output
After each composer install or composer update, you'll see a summary box showing what was created, updated, skipped (locally modified), or removed.
๐ก๏ธ Local Modification Protection
The package tracks known file checksums via a manifest. When syncing:
- New files are created automatically
- Updated files are overwritten only if your local copy matches a known version
- Locally modified files are skipped and flagged โ your changes are preserved
- Orphaned files (removed from config) are cleaned up
To accept the package version of a locally modified file, delete your local copy and run composer update.
Your own .gitignore rules
The shipped .gitignore ends with one marker line:
# mike-bronner/laravel-development-settings: project entries go below this line. Anything above it is lost on the next sync.
The package owns everything above that line and replaces it on every sync. Everything below it is yours: the sync never changes it, and the upstream workflow never proposes it. Put your own rules there. Because they come last, they win, so !AGENTS.md below the marker keeps a hand-written AGENTS.md in git even though the shipped rules ignore it.
- A
.gitignorewith no marker that is exactly a version this package shipped gets the marker automatically. - A
.gitignorewith no marker and your own edits is left alone. An interactivecomposer updateoffers to add the marker (default no), and moves your whole file, unchanged, below it. A non-interactive run only warns. Nothing is proposed upstream from it either way. - A
.gitignoreholding the marker twice is not touched at all, because the sync cannot tell where your part starts. Keep one marker line and runcomposer updateagain. - An edit above the marker is treated like any other local modification: flagged, kept unless you choose to overwrite it, and proposed upstream. Overwriting replaces only the part above the marker.
- If the package stops shipping a file with a marker, it is removed only when the part above the marker is a version this package shipped and nothing sits below it. With your own rules below the marker, it is kept, and an interactive
composer updateasks whether to delete it.
Which files work this way is set by paths.managed in the package config.
PHP_CodeSniffer
The package ships phpcs.xml, which runs the CleanCode standard from mike-bronner/clean-code over the whole project. It skips bootstrap/cache, node_modules, public, storage and vendor at the project root. So vendor/bin/phpcs needs no arguments, in an application and in a package alike. Paths given on the command line replace the project root for that run.
phpcs.xml is a synced file like pint.json. An edited copy is kept as a local modification, and the upstream workflow proposes the edit to this package.
The shipped pint.json writes what phpcs.xml asks for, so the two never undo each other: new Foo() always carries its parentheses (new class () โฆ for an anonymous class), an empty body opens and closes on lines of its own, and imports are grouped as classes, then functions, then constants. Where the two disagreed, phpcs.xml won.
Releases before 0.3.3 shipped phpcs.xml pointing at .php-codesniffer/MikeBronner/ruleset.xml, and 0.3.3 removed both. An unmodified old phpcs.xml is a known version, so the next update replaces it. The old ruleset is still removed when unmodified.
CleanCode is found by name only when the PHP_CodeSniffer installer plugin has run. Composer refuses to install this package until your composer.json decides on that plugin, and false leaves CleanCode unregistered. Allow it:
"config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } }
โ๏ธ Configuration
All behavior is driven by config/development-settings.php within the package. It defines:
paths.directoriesโ directories to sync (recursively)paths.filesโ individual files to syncpaths.managedโ tracked files the project shares with the package at one marker line (.gitignore)paths.legacy_symlinksโ project-root symlinks from older releases, removed on upgradepaths.ignoreโ file and directory names excluded from discovery anywhere in the treehooksโ the Boost composition command and its progress labelcaptureโ package directories whose installed copies are checked for local edits before an update (resources/boost)
A tracked entry comes in two shapes. A plain one names a single path, which the package and your project both use:
'files' => [ 'pint.json', ],
A keyed one reads source => target: the package ships the file on the left and your project receives it on the right.
'files' => [ 'resources/project/gitignore' => '.gitignore', ],
That is how the ignore rules shipped to you stay separate from the package's own .gitignore, which is a different file with a different job. Both shapes work for paths.directories as well.
Nothing downstream changes with it: the manifest, the modification check and the orphan cleanup all key on the target, so a source can move or be renamed inside the package without your project seeing anything.
The shared guidelines and skills are not listed here. They ship at resources/boost/ and Boost reads them out of vendor, so the plugin never copies or tracks them.
See the config file for the current values.
๐ Bidirectional Sync
Changes flow both directions between this package and consuming repositories.
Downstream (Package โ Repos)
- Changes are merged to this repo and a new version is tagged
- Consumer repos run
composer update - The plugin syncs files automatically, and Composer installs the tooling this package requires
Upstream (Repos โ Package)
- A developer edits a tracked file in their project
- On push to
main, a GitHub Action detects changes to tracked files - A PR is automatically created on this repo
- After human review and merge, a new release distributes the changes
The upstream workflow reads tracked paths directly from the package config โ no hardcoded file lists to maintain. It reads both halves of each entry, so a file you edit at .gitignore goes back to the package as resources/project/gitignore rather than overwriting the package's own ignore rules. From .gitignore it takes only the part above the sync marker, so your own rules below it stay in your project.
This flow covers the copied config files only. Guidelines and skills are no longer copied into consuming projects, so a change to them is made here and released downstream; a guideline a project writes in its own .ai/guidelines stays that project's.
Contributing edits made in vendor
The guidelines and skills are read out of vendor/mike-bronner/laravel-development-settings/resources/boost. A fix made there in place is lost at the next composer update, which replaces vendor, and no commit in your project carries it.
So before every composer update, the plugin compares each installed file under resources/boost with every version this package ever shipped. The check is local and uses checksums only. When a file matches none, the plugin lists it, and:
- Interactively, it asks whether to contribute the edits before updating. The default is no. Yes opens a pull request on this repository from a fresh clone.
- Non-interactively, it names the files and the command, and opens nothing.
Run vendor/bin/dev-settings-contribute.php (or composer dev-settings:contribute) to open the pull request yourself. It authenticates with DEVELOPER_SETTINGS_TOKEN, or with a gh-authenticated git.
Setup
- Create a GitHub Personal Access Token with
reposcope - Add it as
DEVELOPER_SETTINGS_TOKENsecret to your repository (or org-level)
When to Use Each Flow
- Edit in your repo โ quick fixes, typo corrections, rule tweaks discovered while coding
- Direct PR to this repo โ major additions, new guidelines, structural changes
๐ Manifest Management
The manifest.json tracks every known checksum of all managed files. It is how the plugin knows whether a local file was modified by you or matches a known version, and which removed-upstream files are safe to clean up. It is append-only (it retains entries for deleted files so downstream cleanup keeps working) and generated โ never hand-edited.
When releasing a new version:
- Update the source files (
config/development-settings.phppaths if adding/removing) - If Boost changed its
php/coreguideline, runcomposer dev-settings:guidelineto regenerateresources/boost/guidelines/05-php.blade.phpfrom the installed Boost - Run
composer dev-settings:manifestto regeneratemanifest.json,capture-manifest.jsonandpackage-manifest.json - Commit and tag a new release
capture-manifest.json is its sibling for the guideline and skill sources under resources/boost, keyed on package paths. It only feeds the edit check above. It is kept out of manifest.json on purpose: copy-sync and orphan cleanup read that file on project paths, and a resources/boost/โฆ key there would let cleanup delete a consuming package's own resources/boost files. package-manifest.json holds the known versions of the files only a package receives: the artisan shim and the .gitattributes. The plugin reads it only in a package with Testbench. Kept in manifest.json, the artisan key would reach every app, where copy-sync would call the app's own artisan locally modified and orphan cleanup would offer to delete it. The same command generates all three, append-only.
CI can guard against a stale manifest with php bin/generate-manifest.php --check (exits non-zero if regenerating any of the files would change anything). php bin/generate-guideline.php --check does the same for the PHP guideline against the installed Boost. It also fails when Boost's php/core no longer holds either PHPDoc line verbatim, and the generator then writes nothing. CI runs both on every pull request, on every push to main, and weekly, so a new Boost release is caught even when nothing here changed.
๐งช Local Development
To test changes before publishing:
# Add to your project's composer.json { "repositories": [ { "type": "path", "url": "../laravel-development-settings" } ] } # Require the local version composer require mike-bronner/laravel-development-settings:@dev
The package is symlinked, so changes are reflected immediately.