From a5b53be461606f38497f0057357180972e5d829d Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Sat, 1 Nov 2025 23:16:58 +0530 Subject: [PATCH 01/21] Add Sail Support in Guideline Signed-off-by: Pushpak Chhajed --- .ai/enforce-tests.blade.php | 2 +- .ai/filament/3/core.blade.php | 2 +- .ai/filament/core.blade.php | 2 +- .ai/folio/core.blade.php | 10 +- .ai/foundation.blade.php | 2 +- .ai/laravel/11/core.blade.php | 6 +- .ai/laravel/core.blade.php | 10 +- .ai/livewire/core.blade.php | 2 +- .ai/pest/core.blade.php | 8 +- .ai/phpunit/core.blade.php | 8 +- .ai/pint/core.blade.php | 4 +- .ai/sail/core.blade.php | 11 ++ .ai/volt/core.blade.php | 2 +- all.php | 125 ------------------ rector.php | 4 +- src/Console/InstallCommand.php | 25 ++-- src/Install/GuidelineAssist.php | 31 ++++- src/Install/GuidelineComposer.php | 8 +- src/Install/Sail.php | 23 ++++ .../Feature/Install/GuidelineComposerTest.php | 47 ++++++- 20 files changed, 146 insertions(+), 186 deletions(-) create mode 100644 .ai/sail/core.blade.php delete mode 100644 all.php create mode 100644 src/Install/Sail.php diff --git a/.ai/enforce-tests.blade.php b/.ai/enforce-tests.blade.php index 3b05821e..b2e79fd7 100644 --- a/.ai/enforce-tests.blade.php +++ b/.ai/enforce-tests.blade.php @@ -1,4 +1,4 @@ ## Test Enforcement - Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. -- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test` with a specific filename or filter. +- Run the minimum number of tests needed to ensure code quality and speed. Use `{{ $assist->artisan() }} test` with a specific filename or filter. diff --git a/.ai/filament/3/core.blade.php b/.ai/filament/3/core.blade.php index 68f4d2b0..4d53967e 100644 --- a/.ai/filament/3/core.blade.php +++ b/.ai/filament/3/core.blade.php @@ -7,5 +7,5 @@ - Tables use the `Tables\Columns` namespace for table columns. - A new `Filament\Forms\Components\RichEditor` component is available. - Form and table schemas now use fluent method chaining. -- Added `php artisan filament:optimize` command for production optimization. +- Added `{{ $assist->artisan() }} filament:optimize` command for production optimization. - Requires implementing `FilamentUser` contract for production access control. diff --git a/.ai/filament/core.blade.php b/.ai/filament/core.blade.php index a5a36ee9..38b57d2d 100644 --- a/.ai/filament/core.blade.php +++ b/.ai/filament/core.blade.php @@ -5,7 +5,7 @@ - Utilize static `make()` methods for consistent component initialization. ### Artisan -- You must use the Filament specific Artisan commands to create new files or components for Filament. You can find these with the `list-artisan-commands` tool, or with `php artisan` and the `--help` option. +- You must use the Filament specific Artisan commands to create new files or components for Filament. You can find these with the `list-artisan-commands` tool, or with `{{ $assist->artisan() }}` and the `--help` option. - Inspect the required options, always pass `--no-interaction`, and valid arguments for other options when applicable. ### Filament's Core Features diff --git a/.ai/folio/core.blade.php b/.ai/folio/core.blade.php index b4ac9c17..5263e8fd 100644 --- a/.ai/folio/core.blade.php +++ b/.ai/folio/core.blade.php @@ -4,20 +4,18 @@ - `pages/index.blade.php` → `/` - `pages/profile/index.blade.php` → `/profile` - `pages/auth/login.blade.php` → `/auth/login` -- You may list available Folio routes using `php artisan folio:list` or using Boost's `list-routes` tool. +- You may list available Folio routes using `{{ $assist->artisan() }} folio:list` or using Boost's `list-routes` tool. ### New Pages & Routes -- Always create new `folio` pages and routes using `artisan folio:page [name]` following existing naming conventions. +- Always create new `folio` pages and routes using `{{ $assist->artisan() }} folio:page [name]` following existing naming conventions. -@verbatim // Creates: resources/views/pages/products.blade.php → /products - php artisan folio:page 'products' + {{ $assist->artisan() }} folio:page 'products' // Creates: resources/views/pages/products/[id].blade.php → /products/{id} - php artisan folio:page 'products/[id]' + {{ $assist->artisan() }} folio:page 'products/[id]' -@endverbatim - Add a 'name' to each new Folio page at the very top of the file so it has a named route available for other parts of the codebase to use. diff --git a/.ai/foundation.blade.php b/.ai/foundation.blade.php index c715fd8a..5b487bfd 100644 --- a/.ai/foundation.blade.php +++ b/.ai/foundation.blade.php @@ -27,7 +27,7 @@ - Do not change the application's dependencies without approval. ## Frontend Bundling -- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->nodePackageManager() }} run build`, `{{ $assist->nodePackageManager() }} run dev`, or `composer run dev`. Ask them. +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->nodePackageManager() }} run build`, `{{ $assist->nodePackageManager() }} run dev`, or `{{ $assist->composer() }} run dev`. Ask them. ## Replies - Be concise in your explanations - focus on what's important rather than explaining obvious details. diff --git a/.ai/laravel/11/core.blade.php b/.ai/laravel/11/core.blade.php index 97f547a8..ee28b469 100644 --- a/.ai/laravel/11/core.blade.php +++ b/.ai/laravel/11/core.blade.php @@ -32,6 +32,6 @@ ### New Artisan Commands - List Artisan commands using Boost's MCP tool, if available. New commands available in Laravel 11: - - `php artisan make:enum` - - `php artisan make:class` - - `php artisan make:interface` + - `{{ $assist->artisan() }} make:enum` + - `{{ $assist->artisan() }} make:class` + - `{{ $assist->artisan() }} make:interface` diff --git a/.ai/laravel/core.blade.php b/.ai/laravel/core.blade.php index de915496..84f8ca41 100644 --- a/.ai/laravel/core.blade.php +++ b/.ai/laravel/core.blade.php @@ -1,7 +1,7 @@ ## Do Things the Laravel Way -- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. -- If you're creating a generic PHP class, use `artisan make:class`. +- Use `{{ $assist->artisan() }} make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. +- If you're creating a generic PHP class, use `{{ $assist->artisan() }} make:class`. - Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. ### Database @@ -12,7 +12,7 @@ - Use Laravel's query builder for very complex database operations. ### Model Creation -- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`. +- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `{{ $assist->artisan() }} make:model`. ### APIs & Eloquent Resources - For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. @@ -36,7 +36,7 @@ ### Testing - When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. - Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. -- When creating tests, make use of `php artisan make:test [options] ` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. +- When creating tests, make use of `{{ $assist->artisan() }} make:test [options] ` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. ### Vite Error -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->nodePackageManager() }} run build` or ask the user to run `{{ $assist->nodePackageManager() }} run dev` or `composer run dev`. +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->nodePackageManager() }} run build` or ask the user to run `{{ $assist->nodePackageManager() }} run dev` or `{{ $assist->composer() }} run dev`. diff --git a/.ai/livewire/core.blade.php b/.ai/livewire/core.blade.php index 9d7e532e..d3eba58a 100644 --- a/.ai/livewire/core.blade.php +++ b/.ai/livewire/core.blade.php @@ -1,6 +1,6 @@ ## Livewire Core - Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. -- Use the `php artisan make:livewire [Posts\\CreatePost]` artisan command to create new components +- Use the `{{ $assist->artisan() }} make:livewire [Posts\\CreatePost]` artisan command to create new components - State should live on the server, with the UI reflecting it. - All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. diff --git a/.ai/pest/core.blade.php b/.ai/pest/core.blade.php index 818b16d6..91559128 100644 --- a/.ai/pest/core.blade.php +++ b/.ai/pest/core.blade.php @@ -4,7 +4,7 @@ - If you need to verify a feature is working, write or update a Unit / Feature test. ### Pest Tests -- All tests must be written using Pest. Use `php artisan make:test --pest `. +- All tests must be written using Pest. Use `{{ $assist->artisan() }} make:test --pest `. - You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. - Tests should test all of the happy paths, failure paths, and weird paths. - Tests live in the `tests/Feature` and `tests/Unit` directories. @@ -17,9 +17,9 @@ ### Running Tests - Run the minimal number of tests using an appropriate filter before finalizing code edits. -- To run all tests: `php artisan test`. -- To run all tests in a file: `php artisan test tests/Feature/ExampleTest.php`. -- To filter on a particular test name: `php artisan test --filter=testName` (recommended after making a change to a related file). +- To run all tests: `{{ $assist->artisan() }} test`. +- To run all tests in a file: `{{ $assist->artisan() }} test tests/Feature/ExampleTest.php`. +- To filter on a particular test name: `{{ $assist->artisan() }} test --filter=testName` (recommended after making a change to a related file). - When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. ### Pest Assertions diff --git a/.ai/phpunit/core.blade.php b/.ai/phpunit/core.blade.php index 946ae9f2..d0cbdb5a 100644 --- a/.ai/phpunit/core.blade.php +++ b/.ai/phpunit/core.blade.php @@ -1,6 +1,6 @@ ## PHPUnit Core -- This application uses PHPUnit for testing. All tests must be written as PHPUnit classes. Use `php artisan make:test --phpunit ` to create a new test. +- This application uses PHPUnit for testing. All tests must be written as PHPUnit classes. Use `{{ $assist->artisan() }} make:test --phpunit ` to create a new test. - If you see a test using "Pest", convert it to PHPUnit. - Every time a test has been updated, run that singular test. - When the tests relating to your feature are passing, ask the user if they would like to also run the entire test suite to make sure everything is still passing. @@ -9,6 +9,6 @@ ### Running Tests - Run the minimal number of tests, using an appropriate filter, before finalizing. -- To run all tests: `php artisan test`. -- To run all tests in a file: `php artisan test tests/Feature/ExampleTest.php`. -- To filter on a particular test name: `php artisan test --filter=testName` (recommended after making a change to a related file). +- To run all tests: `{{ $assist->artisan() }} test`. +- To run all tests in a file: `{{ $assist->artisan() }} test tests/Feature/ExampleTest.php`. +- To filter on a particular test name: `{{ $assist->artisan() }} test --filter=testName` (recommended after making a change to a related file). diff --git a/.ai/pint/core.blade.php b/.ai/pint/core.blade.php index 0283e0ac..3cf333b3 100644 --- a/.ai/pint/core.blade.php +++ b/.ai/pint/core.blade.php @@ -1,4 +1,4 @@ ## Laravel Pint Code Formatter -- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style. -- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues. +- You must run `{{ $assist->bin() }}pint --dirty` before finalizing changes to ensure your code matches the project's expected style. +- Do not run `{{ $assist->bin() }}pint --test`, simply run `{{ $assist->bin() }}pint` to fix any formatting issues. diff --git a/.ai/sail/core.blade.php b/.ai/sail/core.blade.php new file mode 100644 index 00000000..d5e410b8 --- /dev/null +++ b/.ai/sail/core.blade.php @@ -0,0 +1,11 @@ +## Laravel Sail + +- This project runs inside Laravel Sail's Docker containers.** You MUST execute all commands through Sail. +- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`. +- Open the application in the browser by running `vendor/bin/sail open`. +- Always prefix PHP, Artisan, Composer, and Node commands** with `vendor/bin/sail`. Examples: + - Run migrations: `vendor/bin/sail artisan migrate` + - Install Composer packages: `vendor/bin/sail composer install` + - Run npm: `vendor/bin/sail npm run dev` + - Execute PHP scripts: `vendor/bin/sail php [script]` +- View all available Sail commands by running `vendor/bin/sail` without arguments. diff --git a/.ai/volt/core.blade.php b/.ai/volt/core.blade.php index 483ee765..654251c4 100644 --- a/.ai/volt/core.blade.php +++ b/.ai/volt/core.blade.php @@ -1,7 +1,7 @@ ## Livewire Volt - This project uses Livewire Volt for interactivity within its pages. New pages requiring interactivity must also use Livewire Volt. There is documentation available for it. -- Make new Volt components using `php artisan make:volt [name] [--test] [--pest]` +- Make new Volt components using `{{ $assist->artisan() }} make:volt [name] [--test] [--pest]` - Volt is a **class-based** and **functional** API for Livewire that supports single-file components, allowing a component's PHP logic and Blade templates to co-exist in the same file - Livewire Volt allows PHP logic and Blade templates in one file. Components use the @verbatim`@volt`@endverbatim directive. - You must check existing Volt components to determine if they're functional or class based. If you can't detect that, ask the user which they prefer before writing a Volt component. diff --git a/all.php b/all.php deleted file mode 100644 index 1d5f4043..00000000 --- a/all.php +++ /dev/null @@ -1,125 +0,0 @@ - [ - 'APP_URL=http://localhost.test', - ], -]), options: ['enables_package_discoveries' => false]); - -// Create a mock Roster that returns ALL packages from .ai/ directory -$mockRoster = new class extends Roster -{ - public function packages(): \Laravel\Roster\PackageCollection - { - $packages = []; - - // Find all package directories in .ai/ - $directories = glob(__DIR__.'/.ai/*', GLOB_ONLYDIR); - - foreach ($directories as $dir) { - $packageName = basename($dir); - - // Skip special directories handled elsewhere in GuidelineComposer - if (in_array($packageName, ['boost', 'herd'], true)) { - continue; - } - - // Map directory names to Roster enum values where they exist - $enumMapping = [ - 'php' => \Laravel\Roster\Enums\Packages::LARAVEL, // Use Laravel as placeholder for php - 'laravel' => \Laravel\Roster\Enums\Packages::LARAVEL, - 'filament' => \Laravel\Roster\Enums\Packages::FILAMENT, - 'fluxui-free' => \Laravel\Roster\Enums\Packages::FLUXUI_FREE, - 'fluxui-pro' => \Laravel\Roster\Enums\Packages::FLUXUI_PRO, - 'inertia-laravel' => \Laravel\Roster\Enums\Packages::INERTIA_LARAVEL, - 'inertia-react' => \Laravel\Roster\Enums\Packages::INERTIA_REACT, - 'inertia-vue' => \Laravel\Roster\Enums\Packages::INERTIA_VUE, - 'livewire' => \Laravel\Roster\Enums\Packages::LIVEWIRE, - 'pest' => \Laravel\Roster\Enums\Packages::PEST, - 'phpunit' => \Laravel\Roster\Enums\Packages::PHPUNIT, - 'pint' => \Laravel\Roster\Enums\Packages::PINT, - 'volt' => \Laravel\Roster\Enums\Packages::VOLT, - 'folio' => \Laravel\Roster\Enums\Packages::FOLIO, - 'pennant' => \Laravel\Roster\Enums\Packages::PENNANT, - 'tailwindcss' => \Laravel\Roster\Enums\Packages::TAILWINDCSS, - ]; - - if (isset($enumMapping[$packageName])) { - // Find ALL version directories and create a package for each - $versionDirs = glob(__DIR__."/.ai/{$packageName}/*", GLOB_ONLYDIR); - if (! empty($versionDirs)) { - $versions = array_map('basename', $versionDirs); - sort($versions, SORT_NUMERIC); - - // Create a package instance for each version found - foreach ($versions as $versionNumber) { - $packages[] = new \Laravel\Roster\Package( - $enumMapping[$packageName], - $packageName, - $versionNumber.'.0.0', - false - ); - } - } else { - // No version directories, just add the core package - $packages[] = new \Laravel\Roster\Package( - $enumMapping[$packageName], - $packageName, - '1.0.0', - false - ); - } - } - } - - return new \Laravel\Roster\PackageCollection($packages); - } -}; - -$herd = new Herd; - -// Create GuidelineComposer with all config options enabled to get ALL guidelines -$config = new GuidelineConfig; -$config->laravelStyle = true; -$config->hasAnApi = true; -$config->caresAboutLocalization = true; -$config->enforceTests = true; - -// Use the real GuidelineComposer with our mock Roster - this will use the exact same ordering logic -$composer = new GuidelineComposer($mockRoster, $herd); -$composer->config($config); - -// Get the guidelines that GuidelineComposer would normally find -$guidelines = $composer->guidelines(); - -// Add missing PHP versions (since GuidelineComposer only adds current PHP version) -$reflection = new ReflectionClass($composer); -$guidelineDirMethod = $reflection->getMethod('guidelinesDir'); -$guidelineDirMethod->setAccessible(true); - -$phpVersions = ['8.1', '8.2', '8.3', '8.4']; -$currentPhp = PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION; - -foreach ($phpVersions as $phpVersion) { - if ($phpVersion !== $currentPhp) { - $content = $guidelineDirMethod->invoke($composer, "php/{$phpVersion}"); - if (! empty($content)) { - $guidelines->put("php/v{$phpVersion}", $content); - } - } -} - -// Now compose ALL guidelines (original + missing PHP versions) -echo GuidelineComposer::composeGuidelines($guidelines); diff --git a/rector.php b/rector.php index 8df4c89b..b9c1690f 100644 --- a/rector.php +++ b/rector.php @@ -17,9 +17,7 @@ ReadOnlyPropertyRector::class, EncapsedStringsToSprintfRector::class, DisallowedEmptyRuleFixerRector::class, - FunctionLikeToFirstClassCallableRector::class => [ - __DIR__.'src/Install/CodeEnvironmentsDetector.php', - ], + FunctionLikeToFirstClassCallableRector::class, ]) ->withPreparedSets( deadCode: true, diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index d745aec8..5078e9d7 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -19,6 +19,7 @@ use Laravel\Boost\Install\GuidelineConfig; use Laravel\Boost\Install\GuidelineWriter; use Laravel\Boost\Install\Herd; +use Laravel\Boost\Install\Sail; use Laravel\Boost\Support\Config; use Laravel\Prompts\Concerns\Colors; use Laravel\Prompts\Terminal; @@ -40,6 +41,8 @@ class InstallCommand extends Command private Herd $herd; + private Sail $sail; + private Terminal $terminal; /** @var Collection */ @@ -74,9 +77,9 @@ public function __construct(protected Config $config) parent::__construct(); } - public function handle(CodeEnvironmentsDetector $codeEnvironmentsDetector, Herd $herd, Terminal $terminal): void + public function handle(CodeEnvironmentsDetector $codeEnvironmentsDetector, Herd $herd, Sail $sail, Terminal $terminal): void { - $this->bootstrap($codeEnvironmentsDetector, $herd, $terminal); + $this->bootstrap($codeEnvironmentsDetector, $herd, $sail, $terminal); $this->displayBoostHeader(); $this->discoverEnvironment(); @@ -85,10 +88,11 @@ public function handle(CodeEnvironmentsDetector $codeEnvironmentsDetector, Herd $this->outro(); } - protected function bootstrap(CodeEnvironmentsDetector $codeEnvironmentsDetector, Herd $herd, Terminal $terminal): void + protected function bootstrap(CodeEnvironmentsDetector $codeEnvironmentsDetector, Herd $herd, Sail $sail, Terminal $terminal): void { $this->codeEnvironmentsDetector = $codeEnvironmentsDetector; $this->herd = $herd; + $this->sail = $sail; $this->terminal = $terminal; $this->terminal->initDimensions(); @@ -241,7 +245,7 @@ protected function selectBoostFeatures(): Collection $features->push('herd_mcp'); } - if ($this->isSailInstalled() && ($this->isRunningInsideSail() || $this->shouldConfigureSail())) { + if ($this->sail->isInstalled() && ($this->sail->isRunningInside() || $this->shouldConfigureSail())) { $features->push('sail'); } @@ -489,21 +493,10 @@ protected function shouldUseSail(): bool return $this->selectedBoostFeatures->contains('sail'); } - protected function isSailInstalled(): bool - { - return file_exists(base_path('vendor/bin/sail')) && - (file_exists(base_path('docker-compose.yml')) || file_exists(base_path('compose.yaml'))); - } - - protected function isRunningInsideSail(): bool - { - return get_current_user() === 'sail' || getenv('LARAVEL_SAIL') === '1'; - } - protected function buildMcpCommand(McpClient $mcpClient): array { if ($this->shouldUseSail()) { - return ['laravel-boost', './vendor/bin/sail', 'artisan', 'boost:mcp']; + return ['laravel-boost', Sail::SAIL_BINARY_PATH, 'artisan', 'boost:mcp']; } $inWsl = $this->isRunningInWsl(); diff --git a/src/Install/GuidelineAssist.php b/src/Install/GuidelineAssist.php index 6b06f12f..6e93608a 100644 --- a/src/Install/GuidelineAssist.php +++ b/src/Install/GuidelineAssist.php @@ -6,8 +6,8 @@ use Illuminate\Database\Eloquent\Model; use Laravel\Boost\Install\Assists\Inertia; +use Laravel\Boost\Support\Config as BoostConfig; use Laravel\Roster\Enums\NodePackageManager; -use Laravel\Roster\Enums\Packages; use Laravel\Roster\Roster; use ReflectionClass; use Symfony\Component\Finder\Finder; @@ -24,8 +24,11 @@ class GuidelineAssist protected static array $classes = []; + public bool $usesSail; + public function __construct(public Roster $roster) { + $this->usesSail = (new BoostConfig)->getSail(); $this->modelPaths = $this->discover(fn ($reflection): bool => ($reflection->isSubclassOf(Model::class) && ! $reflection->isAbstract())); $this->controllerPaths = $this->discover(fn (ReflectionClass $reflection): bool => (stripos($reflection->getName(), 'controller') !== false || stripos($reflection->getNamespaceName(), 'controller') !== false)); $this->enumPaths = $this->discover(fn ($reflection) => $reflection->isEnum()); @@ -160,11 +163,6 @@ public function enumContents(): string return file_get_contents(current($this->enumPaths)); } - public function packageGte(Packages $package, string $version): bool - { - return $this->roster->usesVersion($package, $version, '>='); - } - public function inertia(): Inertia { return new Inertia($this->roster); @@ -174,4 +172,25 @@ public function nodePackageManager(): string { return ($this->roster->nodePackageManager() ?? NodePackageManager::NPM)->value; } + + public function artisan(): string + { + return $this->usesSail + ? Sail::SAIL_BINARY_PATH.' artisan' + : 'php artisan'; + } + + public function composer(): string + { + return $this->usesSail + ? Sail::SAIL_BINARY_PATH.' composer' + : 'composer'; + } + + public function bin(): string + { + return $this->usesSail + ? Sail::SAIL_BINARY_PATH.' bin' + : 'vendor/bin/'; + } } diff --git a/src/Install/GuidelineComposer.php b/src/Install/GuidelineComposer.php index 1a14f45c..42f5a2da 100644 --- a/src/Install/GuidelineComposer.php +++ b/src/Install/GuidelineComposer.php @@ -44,7 +44,7 @@ class GuidelineComposer Packages::MCP, ]; - public function __construct(protected Roster $roster, protected Herd $herd) + public function __construct(protected Roster $roster, protected Herd $herd, protected Sail $sail) { $this->packagePriorities = [ Packages::PEST->value => [Packages::PHPUNIT->value], @@ -125,10 +125,14 @@ protected function find(): Collection // $phpMajorMinor = PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION; // $guidelines->put('php/v'.$phpMajorMinor, $this->guidelinesDir('php/'.$phpMajorMinor)); - if (str_contains((string) config('app.url'), '.test') && $this->herd->isInstalled()) { + if (str_contains((string) config('app.url'), '.test') && $this->herd->isInstalled() && ! $this->sail->isInstalled()) { $guidelines->put('herd', $this->guideline('herd/core')); } + if ($this->sail->isInstalled()) { + $guidelines->put('sail', $this->guideline('sail/core')); + } + if ($this->config->laravelStyle) { $guidelines->put('laravel/style', $this->guideline('laravel/style')); } diff --git a/src/Install/Sail.php b/src/Install/Sail.php new file mode 100644 index 00000000..f58627c1 --- /dev/null +++ b/src/Install/Sail.php @@ -0,0 +1,23 @@ +herd = Mockery::mock(Herd::class); $this->herd->shouldReceive('isInstalled')->andReturn(false)->byDefault(); + $this->sail = Mockery::mock(Sail::class); + $this->sail->shouldReceive('isInstalled')->andReturn(false)->byDefault(); + // Bind the mock to the service container so it's used everywhere $this->app->instance(Roster::class, $this->roster); - $this->composer = new GuidelineComposer($this->roster, $this->herd); + $this->composer = new GuidelineComposer($this->roster, $this->herd, $this->sail); }); test('includes Inertia React conditional guidelines based on version', function (string $version, bool $shouldIncludeForm, bool $shouldInclude212Features): void { @@ -155,6 +159,41 @@ 'localhost with Herd' => ['http://localhost:8000', true, false], ]); +test('excludes Herd guidelines when Sail is installed', function (): void { + $packages = new PackageCollection([ + new Package(Packages::LARAVEL, 'laravel/framework', '11.0.0'), + ]); + + $this->roster->shouldReceive('packages')->andReturn($packages); + $this->herd->shouldReceive('isInstalled')->andReturn(true); + $this->sail->shouldReceive('isInstalled')->andReturn(true); + + $guidelines = $this->composer->compose(); + + expect($guidelines) + ->not->toContain('Laravel Herd') + ->toContain('Laravel Sail'); + +}); + +test('excludes Sail guidelines when Herd is installed', function (): void { + $packages = new PackageCollection([ + new Package(Packages::LARAVEL, 'laravel/framework', '11.0.0'), + ]); + + $this->roster->shouldReceive('packages')->andReturn($packages); + $this->herd->shouldReceive('isInstalled')->andReturn(true); + $this->sail->shouldReceive('isInstalled')->andReturn(false); + + config(['app.url' => 'http://myapp.test']); + + $guidelines = $this->composer->compose(); + + expect($guidelines) + ->toContain('Laravel Herd') + ->not->toContain('Laravel Sail'); +}); + test('composes guidelines with proper formatting', function (): void { $packages = new PackageCollection([ new Package(Packages::LARAVEL, 'laravel/framework', '11.0.0'), @@ -267,7 +306,7 @@ $this->roster->shouldReceive('packages')->andReturn($packages); - $composer = Mockery::mock(GuidelineComposer::class, [$this->roster, $this->herd])->makePartial(); + $composer = Mockery::mock(GuidelineComposer::class, [$this->roster, $this->herd, $this->sail])->makePartial(); $composer ->shouldReceive('customGuidelinePath') ->andReturnUsing(fn ($path = ''): string => realpath(testDirectory('fixtures/.ai/guidelines')).'/'.ltrim((string) $path, '/')); @@ -290,7 +329,7 @@ $this->roster->shouldReceive('packages')->andReturn($packages); - $composer = Mockery::mock(GuidelineComposer::class, [$this->roster, $this->herd])->makePartial(); + $composer = Mockery::mock(GuidelineComposer::class, [$this->roster, $this->herd, $this->sail])->makePartial(); $composer ->shouldReceive('customGuidelinePath') ->andReturnUsing(fn ($path = ''): string => realpath(testDirectory('fixtures/.ai/guidelines')).'/'.ltrim((string) $path, '/')); @@ -393,7 +432,7 @@ $this->roster->shouldReceive('packages')->andReturn($packages); $this->nodePackageManager = NodePackageManager::NPM; - $composer = Mockery::mock(GuidelineComposer::class, [$this->roster, $this->herd])->makePartial(); + $composer = Mockery::mock(GuidelineComposer::class, [$this->roster, $this->herd, $this->sail])->makePartial(); $composer ->shouldReceive('customGuidelinePath') ->andReturnUsing(fn ($path = ''): string => realpath(testDirectory('fixtures/.ai/guidelines')).'/'.ltrim((string) $path, '/')); From e0142c7786db700b73ffff0a7d56a7cd94e3be31 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Sat, 1 Nov 2025 23:31:19 +0530 Subject: [PATCH 02/21] Fix Markdown Signed-off-by: Pushpak Chhajed --- .ai/sail/core.blade.php | 2 +- tests/Feature/Install/GuidelineComposerTest.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.ai/sail/core.blade.php b/.ai/sail/core.blade.php index d5e410b8..43551993 100644 --- a/.ai/sail/core.blade.php +++ b/.ai/sail/core.blade.php @@ -1,6 +1,6 @@ ## Laravel Sail -- This project runs inside Laravel Sail's Docker containers.** You MUST execute all commands through Sail. +- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail. - Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`. - Open the application in the browser by running `vendor/bin/sail open`. - Always prefix PHP, Artisan, Composer, and Node commands** with `vendor/bin/sail`. Examples: diff --git a/tests/Feature/Install/GuidelineComposerTest.php b/tests/Feature/Install/GuidelineComposerTest.php index 200c6f6c..000c8aee 100644 --- a/tests/Feature/Install/GuidelineComposerTest.php +++ b/tests/Feature/Install/GuidelineComposerTest.php @@ -168,6 +168,8 @@ $this->herd->shouldReceive('isInstalled')->andReturn(true); $this->sail->shouldReceive('isInstalled')->andReturn(true); + config(['app.url' => 'http://myapp.test']); + $guidelines = $this->composer->compose(); expect($guidelines) From c4e4e56969fa7bdaeb3f872e2c06a8b8107898de Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Tue, 4 Nov 2025 22:27:46 +0530 Subject: [PATCH 03/21] Refactor Signed-off-by: Pushpak Chhajed --- .ai/enforce-tests.blade.php | 3 ++ .ai/filament/3/core.blade.php | 3 ++ .ai/filament/core.blade.php | 3 ++ .ai/folio/core.blade.php | 3 ++ .ai/foundation.blade.php | 7 ++++ .ai/laravel/core.blade.php | 7 ++++ .ai/livewire/core.blade.php | 3 ++ .ai/pest/core.blade.php | 4 ++- .ai/phpunit/core.blade.php | 3 ++ .ai/pint/core.blade.php | 3 ++ .ai/volt/core.blade.php | 3 ++ src/Console/InstallCommand.php | 3 +- src/Install/GuidelineAssist.php | 14 +++----- src/Install/GuidelineComposer.php | 17 ++++++---- src/Install/GuidelineConfig.php | 2 ++ src/Install/Sail.php | 2 +- .../Feature/Install/GuidelineComposerTest.php | 32 +++++++++++-------- 17 files changed, 79 insertions(+), 33 deletions(-) diff --git a/.ai/enforce-tests.blade.php b/.ai/enforce-tests.blade.php index b2e79fd7..91b0f9c4 100644 --- a/.ai/enforce-tests.blade.php +++ b/.ai/enforce-tests.blade.php @@ -1,3 +1,6 @@ +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp ## Test Enforcement - Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. diff --git a/.ai/filament/3/core.blade.php b/.ai/filament/3/core.blade.php index 4d53967e..9c08aded 100644 --- a/.ai/filament/3/core.blade.php +++ b/.ai/filament/3/core.blade.php @@ -1,3 +1,6 @@ +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp ## Filament 3 ## Version 3 Changes To Focus On diff --git a/.ai/filament/core.blade.php b/.ai/filament/core.blade.php index 38b57d2d..5591feb7 100644 --- a/.ai/filament/core.blade.php +++ b/.ai/filament/core.blade.php @@ -1,3 +1,6 @@ +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp ## Filament - Filament is used by this application, check how and where to follow existing application conventions. - Filament is a Server-Driven UI (SDUI) framework for Laravel. It allows developers to define user interfaces in PHP using structured configuration objects. It is built on top of Livewire, Alpine.js, and Tailwind CSS. diff --git a/.ai/folio/core.blade.php b/.ai/folio/core.blade.php index 5263e8fd..44f25369 100644 --- a/.ai/folio/core.blade.php +++ b/.ai/folio/core.blade.php @@ -1,3 +1,6 @@ +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp ## Laravel Folio - Laravel Folio is a file based router. With Laravel Folio, a new route is created for every Blade file within the configured Folio directory. For example, pages are usually in in `resources/views/pages/` and the file structure determines routes: diff --git a/.ai/foundation.blade.php b/.ai/foundation.blade.php index 5b487bfd..e6f68439 100644 --- a/.ai/foundation.blade.php +++ b/.ai/foundation.blade.php @@ -1,3 +1,6 @@ +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp # Laravel Boost Guidelines The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to enhance the user's satisfaction building Laravel applications. @@ -27,7 +30,11 @@ - Do not change the application's dependencies without approval. ## Frontend Bundling +@if ($assist->config->enforceSail) +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->composer() }} run dev`. Ask them. +@else - If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->nodePackageManager() }} run build`, `{{ $assist->nodePackageManager() }} run dev`, or `{{ $assist->composer() }} run dev`. Ask them. +@endif ## Replies - Be concise in your explanations - focus on what's important rather than explaining obvious details. diff --git a/.ai/laravel/core.blade.php b/.ai/laravel/core.blade.php index 84f8ca41..ff4818ca 100644 --- a/.ai/laravel/core.blade.php +++ b/.ai/laravel/core.blade.php @@ -1,3 +1,6 @@ +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp ## Do Things the Laravel Way - Use `{{ $assist->artisan() }} make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. @@ -39,4 +42,8 @@ - When creating tests, make use of `{{ $assist->artisan() }} make:test [options] ` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. ### Vite Error +@if ($assist->config->enforceSail) +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->composer() }} run dev` or ask the user to run it. +@else - If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->nodePackageManager() }} run build` or ask the user to run `{{ $assist->nodePackageManager() }} run dev` or `{{ $assist->composer() }} run dev`. +@endif diff --git a/.ai/livewire/core.blade.php b/.ai/livewire/core.blade.php index d3eba58a..07f4c067 100644 --- a/.ai/livewire/core.blade.php +++ b/.ai/livewire/core.blade.php @@ -1,3 +1,6 @@ +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp ## Livewire Core - Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. - Use the `{{ $assist->artisan() }} make:livewire [Posts\\CreatePost]` artisan command to create new components diff --git a/.ai/pest/core.blade.php b/.ai/pest/core.blade.php index 91559128..4586d325 100644 --- a/.ai/pest/core.blade.php +++ b/.ai/pest/core.blade.php @@ -1,5 +1,7 @@ ## Pest - +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp ### Testing - If you need to verify a feature is working, write or update a Unit / Feature test. diff --git a/.ai/phpunit/core.blade.php b/.ai/phpunit/core.blade.php index d0cbdb5a..d4bab859 100644 --- a/.ai/phpunit/core.blade.php +++ b/.ai/phpunit/core.blade.php @@ -1,3 +1,6 @@ +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp ## PHPUnit Core - This application uses PHPUnit for testing. All tests must be written as PHPUnit classes. Use `{{ $assist->artisan() }} make:test --phpunit ` to create a new test. diff --git a/.ai/pint/core.blade.php b/.ai/pint/core.blade.php index 3cf333b3..1e81775b 100644 --- a/.ai/pint/core.blade.php +++ b/.ai/pint/core.blade.php @@ -1,3 +1,6 @@ +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp ## Laravel Pint Code Formatter - You must run `{{ $assist->bin() }}pint --dirty` before finalizing changes to ensure your code matches the project's expected style. diff --git a/.ai/volt/core.blade.php b/.ai/volt/core.blade.php index 654251c4..62b4a0d5 100644 --- a/.ai/volt/core.blade.php +++ b/.ai/volt/core.blade.php @@ -1,3 +1,6 @@ +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp ## Livewire Volt - This project uses Livewire Volt for interactivity within its pages. New pages requiring interactivity must also use Livewire Volt. There is documentation available for it. diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 5078e9d7..fe421310 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -245,7 +245,7 @@ protected function selectBoostFeatures(): Collection $features->push('herd_mcp'); } - if ($this->sail->isInstalled() && ($this->sail->isRunningInside() || $this->shouldConfigureSail())) { + if ($this->sail->isInstalled() && ($this->sail->isActive() || $this->shouldConfigureSail())) { $features->push('sail'); } @@ -407,6 +407,7 @@ protected function installGuidelines(): void $guidelineConfig->caresAboutLocalization = $this->detectLocalization(); $guidelineConfig->hasAnApi = false; $guidelineConfig->aiGuidelines = $this->selectedAiGuidelines->values()->toArray(); + $guidelineConfig->enforceSail = $this->shouldUseSail(); $composer = app(GuidelineComposer::class)->config($guidelineConfig); $guidelines = $composer->guidelines(); diff --git a/src/Install/GuidelineAssist.php b/src/Install/GuidelineAssist.php index 6e93608a..afe925d9 100644 --- a/src/Install/GuidelineAssist.php +++ b/src/Install/GuidelineAssist.php @@ -6,7 +6,6 @@ use Illuminate\Database\Eloquent\Model; use Laravel\Boost\Install\Assists\Inertia; -use Laravel\Boost\Support\Config as BoostConfig; use Laravel\Roster\Enums\NodePackageManager; use Laravel\Roster\Roster; use ReflectionClass; @@ -24,11 +23,8 @@ class GuidelineAssist protected static array $classes = []; - public bool $usesSail; - - public function __construct(public Roster $roster) + public function __construct(public Roster $roster, public GuidelineConfig $config) { - $this->usesSail = (new BoostConfig)->getSail(); $this->modelPaths = $this->discover(fn ($reflection): bool => ($reflection->isSubclassOf(Model::class) && ! $reflection->isAbstract())); $this->controllerPaths = $this->discover(fn (ReflectionClass $reflection): bool => (stripos($reflection->getName(), 'controller') !== false || stripos($reflection->getNamespaceName(), 'controller') !== false)); $this->enumPaths = $this->discover(fn ($reflection) => $reflection->isEnum()); @@ -175,22 +171,22 @@ public function nodePackageManager(): string public function artisan(): string { - return $this->usesSail + return $this->config->enforceSail ? Sail::SAIL_BINARY_PATH.' artisan' : 'php artisan'; } public function composer(): string { - return $this->usesSail + return $this->config->enforceSail ? Sail::SAIL_BINARY_PATH.' composer' : 'composer'; } public function bin(): string { - return $this->usesSail - ? Sail::SAIL_BINARY_PATH.' bin' + return $this->config->enforceSail + ? Sail::SAIL_BINARY_PATH.' bin ' : 'vendor/bin/'; } } diff --git a/src/Install/GuidelineComposer.php b/src/Install/GuidelineComposer.php index 42f5a2da..a9cf8483 100644 --- a/src/Install/GuidelineComposer.php +++ b/src/Install/GuidelineComposer.php @@ -24,8 +24,6 @@ class GuidelineComposer protected GuidelineConfig $config; - protected GuidelineAssist $guidelineAssist; - /** * Package priority system to handle conflicts between packages. * When a higher-priority package is present, lower-priority packages are excluded from guidelines. @@ -44,14 +42,14 @@ class GuidelineComposer Packages::MCP, ]; - public function __construct(protected Roster $roster, protected Herd $herd, protected Sail $sail) + public function __construct(protected Roster $roster, protected Herd $herd) { $this->packagePriorities = [ Packages::PEST->value => [Packages::PHPUNIT->value], Packages::FLUXUI_PRO->value => [Packages::FLUXUI_FREE->value], + Packages::LARAVEL->value => [Packages::SAIL->value], ]; $this->config = new GuidelineConfig; - $this->guidelineAssist = new GuidelineAssist($roster); } public function config(GuidelineConfig $config): self @@ -61,6 +59,11 @@ public function config(GuidelineConfig $config): self return $this; } + protected function assist(): GuidelineAssist + { + return new GuidelineAssist($this->roster, $this->config); + } + /** * Auto discovers the guideline files and composes them into one string. */ @@ -125,11 +128,11 @@ protected function find(): Collection // $phpMajorMinor = PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION; // $guidelines->put('php/v'.$phpMajorMinor, $this->guidelinesDir('php/'.$phpMajorMinor)); - if (str_contains((string) config('app.url'), '.test') && $this->herd->isInstalled() && ! $this->sail->isInstalled()) { + if (str_contains((string) config('app.url'), '.test') && $this->herd->isInstalled() && ! $this->config->enforceSail) { $guidelines->put('herd', $this->guideline('herd/core')); } - if ($this->sail->isInstalled()) { + if ($this->config->enforceSail) { $guidelines->put('sail', $this->guideline('sail/core')); } @@ -274,7 +277,7 @@ protected function renderContent(string $content, string $path): string $content = str_replace(array_keys($placeholders), array_values($placeholders), $content); $rendered = Blade::render($content, [ - 'assist' => $this->guidelineAssist, + 'assist' => $this->assist(), ]); return str_replace(array_values($placeholders), array_keys($placeholders), $rendered); diff --git a/src/Install/GuidelineConfig.php b/src/Install/GuidelineConfig.php index 20baccbf..fa0b1cb7 100644 --- a/src/Install/GuidelineConfig.php +++ b/src/Install/GuidelineConfig.php @@ -10,6 +10,8 @@ class GuidelineConfig public bool $laravelStyle = false; + public bool $enforceSail = false; + public bool $caresAboutLocalization = false; public bool $hasAnApi = false; diff --git a/src/Install/Sail.php b/src/Install/Sail.php index f58627c1..a2e7b5e8 100644 --- a/src/Install/Sail.php +++ b/src/Install/Sail.php @@ -16,7 +16,7 @@ public function isInstalled(): bool (file_exists(base_path('docker-compose.yml')) || file_exists(base_path('compose.yaml'))); } - public function isRunningInside(): bool + public function isActive(): bool { return get_current_user() === 'sail' || getenv('LARAVEL_SAIL') === '1'; } diff --git a/tests/Feature/Install/GuidelineComposerTest.php b/tests/Feature/Install/GuidelineComposerTest.php index 000c8aee..eb7fe8d3 100644 --- a/tests/Feature/Install/GuidelineComposerTest.php +++ b/tests/Feature/Install/GuidelineComposerTest.php @@ -5,7 +5,6 @@ use Laravel\Boost\Install\GuidelineComposer; use Laravel\Boost\Install\GuidelineConfig; use Laravel\Boost\Install\Herd; -use Laravel\Boost\Install\Sail; use Laravel\Roster\Enums\NodePackageManager; use Laravel\Roster\Enums\Packages; use Laravel\Roster\Package; @@ -24,13 +23,10 @@ $this->herd = Mockery::mock(Herd::class); $this->herd->shouldReceive('isInstalled')->andReturn(false)->byDefault(); - $this->sail = Mockery::mock(Sail::class); - $this->sail->shouldReceive('isInstalled')->andReturn(false)->byDefault(); - // Bind the mock to the service container so it's used everywhere $this->app->instance(Roster::class, $this->roster); - $this->composer = new GuidelineComposer($this->roster, $this->herd, $this->sail); + $this->composer = new GuidelineComposer($this->roster, $this->herd); }); test('includes Inertia React conditional guidelines based on version', function (string $version, bool $shouldIncludeForm, bool $shouldInclude212Features): void { @@ -159,18 +155,22 @@ 'localhost with Herd' => ['http://localhost:8000', true, false], ]); -test('excludes Herd guidelines when Sail is installed', function (): void { +test('excludes Herd guidelines when Sail is configured', function (): void { $packages = new PackageCollection([ new Package(Packages::LARAVEL, 'laravel/framework', '11.0.0'), ]); $this->roster->shouldReceive('packages')->andReturn($packages); $this->herd->shouldReceive('isInstalled')->andReturn(true); - $this->sail->shouldReceive('isInstalled')->andReturn(true); config(['app.url' => 'http://myapp.test']); - $guidelines = $this->composer->compose(); + $config = new GuidelineConfig; + $config->enforceSail = true; + + $guidelines = $this->composer + ->config($config) + ->compose(); expect($guidelines) ->not->toContain('Laravel Herd') @@ -178,18 +178,22 @@ }); -test('excludes Sail guidelines when Herd is installed', function (): void { +test('excludes Sail guidelines when Herd is configured', function (): void { $packages = new PackageCollection([ new Package(Packages::LARAVEL, 'laravel/framework', '11.0.0'), ]); $this->roster->shouldReceive('packages')->andReturn($packages); $this->herd->shouldReceive('isInstalled')->andReturn(true); - $this->sail->shouldReceive('isInstalled')->andReturn(false); config(['app.url' => 'http://myapp.test']); - $guidelines = $this->composer->compose(); + $config = new GuidelineConfig; + $config->enforceSail = false; + + $guidelines = $this->composer + ->config($config) + ->compose(); expect($guidelines) ->toContain('Laravel Herd') @@ -308,7 +312,7 @@ $this->roster->shouldReceive('packages')->andReturn($packages); - $composer = Mockery::mock(GuidelineComposer::class, [$this->roster, $this->herd, $this->sail])->makePartial(); + $composer = Mockery::mock(GuidelineComposer::class, [$this->roster, $this->herd])->makePartial(); $composer ->shouldReceive('customGuidelinePath') ->andReturnUsing(fn ($path = ''): string => realpath(testDirectory('fixtures/.ai/guidelines')).'/'.ltrim((string) $path, '/')); @@ -331,7 +335,7 @@ $this->roster->shouldReceive('packages')->andReturn($packages); - $composer = Mockery::mock(GuidelineComposer::class, [$this->roster, $this->herd, $this->sail])->makePartial(); + $composer = Mockery::mock(GuidelineComposer::class, [$this->roster, $this->herd])->makePartial(); $composer ->shouldReceive('customGuidelinePath') ->andReturnUsing(fn ($path = ''): string => realpath(testDirectory('fixtures/.ai/guidelines')).'/'.ltrim((string) $path, '/')); @@ -434,7 +438,7 @@ $this->roster->shouldReceive('packages')->andReturn($packages); $this->nodePackageManager = NodePackageManager::NPM; - $composer = Mockery::mock(GuidelineComposer::class, [$this->roster, $this->herd, $this->sail])->makePartial(); + $composer = Mockery::mock(GuidelineComposer::class, [$this->roster, $this->herd])->makePartial(); $composer ->shouldReceive('customGuidelinePath') ->andReturnUsing(fn ($path = ''): string => realpath(testDirectory('fixtures/.ai/guidelines')).'/'.ltrim((string) $path, '/')); From 4337a779ca574e76ed8daa71d04e2cab3cedcd84 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Tue, 4 Nov 2025 22:32:53 +0530 Subject: [PATCH 04/21] move method Signed-off-by: Pushpak Chhajed --- src/Install/GuidelineComposer.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Install/GuidelineComposer.php b/src/Install/GuidelineComposer.php index a9cf8483..bbb697af 100644 --- a/src/Install/GuidelineComposer.php +++ b/src/Install/GuidelineComposer.php @@ -59,11 +59,6 @@ public function config(GuidelineConfig $config): self return $this; } - protected function assist(): GuidelineAssist - { - return new GuidelineAssist($this->roster, $this->config); - } - /** * Auto discovers the guideline files and composes them into one string. */ @@ -345,6 +340,11 @@ protected function processBoostSnippets(string $content): string }, $content); } + protected function assist(): GuidelineAssist + { + return new GuidelineAssist($this->roster, $this->config); + } + protected function prependPackageGuidelinePath(string $path): string { return $this->prependGuidelinePath($path, __DIR__.'/../../.ai/'); From 1e0962017c9864559bd9f3427a00c744fe356ea1 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Thu, 6 Nov 2025 20:39:48 +0530 Subject: [PATCH 05/21] rename variables Signed-off-by: Pushpak Chhajed --- src/Install/GuidelineComposer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Install/GuidelineComposer.php b/src/Install/GuidelineComposer.php index e6cf7b16..2125e3cb 100644 --- a/src/Install/GuidelineComposer.php +++ b/src/Install/GuidelineComposer.php @@ -48,7 +48,7 @@ class GuidelineComposer * * @var array */ - protected array $excludeByDefault = [ + protected array $manuallyIncludedPackages = [ Packages::SAIL, ]; @@ -220,7 +220,7 @@ protected function find(): Collection */ protected function shouldExcludePackage(Package $package): bool { - if (in_array($package->package(), $this->excludeByDefault, true)) { + if (in_array($package->package(), $this->manuallyIncludedPackages, true)) { return true; } @@ -281,7 +281,7 @@ protected function renderContent(string $content, string $path): string $content = str_replace(array_keys($placeholders), array_values($placeholders), $content); $rendered = Blade::render($content, [ - 'assist' => $this->assist(), + 'assist' => $this->getGuidelineAssist(), ]); return str_replace(array_values($placeholders), array_keys($placeholders), $rendered); @@ -349,7 +349,7 @@ protected function processBoostSnippets(string $content): string }, $content); } - protected function assist(): GuidelineAssist + protected function getGuidelineAssist(): GuidelineAssist { return new GuidelineAssist($this->roster, $this->config); } From 3065c86916e73ebc02a183ff1745807988f5cec9 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Thu, 6 Nov 2025 20:42:40 +0530 Subject: [PATCH 06/21] Refactor GuidelineComposer.php Signed-off-by: Pushpak Chhajed --- src/Install/GuidelineComposer.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Install/GuidelineComposer.php b/src/Install/GuidelineComposer.php index 2125e3cb..bd7ab9b1 100644 --- a/src/Install/GuidelineComposer.php +++ b/src/Install/GuidelineComposer.php @@ -48,7 +48,7 @@ class GuidelineComposer * * @var array */ - protected array $manuallyIncludedPackages = [ + protected array $optInPackages = [ Packages::SAIL, ]; @@ -220,11 +220,10 @@ protected function find(): Collection */ protected function shouldExcludePackage(Package $package): bool { - if (in_array($package->package(), $this->manuallyIncludedPackages, true)) { + if (in_array($package->package(), $this->optInPackages, true)) { return true; } - // Check if a higher priority package excludes this one foreach ($this->packagePriorities as $priorityPackage => $excludedPackages) { $packageIsInExclusionList = in_array($package->package()->value, $excludedPackages, true); From b8573a1eb252dfd5426669b1430908292d9223c3 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Fri, 7 Nov 2025 18:22:40 +0530 Subject: [PATCH 07/21] Refactor Sail Command Usage Signed-off-by: Pushpak Chhajed --- .ai/enforce-tests.blade.php | 2 +- .ai/filament/3/core.blade.php | 2 +- .ai/folio/core.blade.php | 8 ++--- .ai/foundation.blade.php | 6 ++-- .ai/laravel/11/core.blade.php | 9 +++-- .ai/laravel/core.blade.php | 14 ++++---- .ai/livewire/core.blade.php | 2 +- .ai/pest/core.blade.php | 8 ++--- .ai/phpunit/core.blade.php | 8 ++--- .ai/pint/core.blade.php | 4 +-- .ai/sail/core.blade.php | 19 ++++++----- .ai/volt/core.blade.php | 2 +- src/Console/InstallCommand.php | 8 +++-- src/Install/GuidelineAssist.php | 34 +++++++++++++------ src/Install/GuidelineComposer.php | 4 +-- src/Install/GuidelineConfig.php | 2 +- src/Install/Sail.php | 29 ++++++++++++++-- .../Feature/Install/GuidelineComposerTest.php | 6 ++-- 18 files changed, 105 insertions(+), 62 deletions(-) diff --git a/.ai/enforce-tests.blade.php b/.ai/enforce-tests.blade.php index 91b0f9c4..53b1a596 100644 --- a/.ai/enforce-tests.blade.php +++ b/.ai/enforce-tests.blade.php @@ -4,4 +4,4 @@ ## Test Enforcement - Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. -- Run the minimum number of tests needed to ensure code quality and speed. Use `{{ $assist->artisan() }} test` with a specific filename or filter. +- Run the minimum number of tests needed to ensure code quality and speed. Use `{{ $assist->artisanCommand('test') }}` with a specific filename or filter. diff --git a/.ai/filament/3/core.blade.php b/.ai/filament/3/core.blade.php index 9c08aded..f80d017d 100644 --- a/.ai/filament/3/core.blade.php +++ b/.ai/filament/3/core.blade.php @@ -10,5 +10,5 @@ - Tables use the `Tables\Columns` namespace for table columns. - A new `Filament\Forms\Components\RichEditor` component is available. - Form and table schemas now use fluent method chaining. -- Added `{{ $assist->artisan() }} filament:optimize` command for production optimization. +- Added `{{ $assist->artisanCommand('filament:optimize') }}` command for production optimization. - Requires implementing `FilamentUser` contract for production access control. diff --git a/.ai/folio/core.blade.php b/.ai/folio/core.blade.php index 44f25369..b238fd96 100644 --- a/.ai/folio/core.blade.php +++ b/.ai/folio/core.blade.php @@ -7,17 +7,17 @@ - `pages/index.blade.php` → `/` - `pages/profile/index.blade.php` → `/profile` - `pages/auth/login.blade.php` → `/auth/login` -- You may list available Folio routes using `{{ $assist->artisan() }} folio:list` or using Boost's `list-routes` tool. +- You may list available Folio routes using `{{ $assist->artisanCommand("folio:list") }}` or using Boost's `list-routes` tool. ### New Pages & Routes -- Always create new `folio` pages and routes using `{{ $assist->artisan() }} folio:page [name]` following existing naming conventions. +- Always create new `folio` pages and routes using `{{ $assist->artisanCommand('folio:page [name]') }}` following existing naming conventions. // Creates: resources/views/pages/products.blade.php → /products - {{ $assist->artisan() }} folio:page 'products' + {{ $assist->artisanCommand("folio:page 'products'") }} // Creates: resources/views/pages/products/[id].blade.php → /products/{id} - {{ $assist->artisan() }} folio:page 'products/[id]' + {{ $assist->artisanCommand("folio:page 'products/[id]'") }} - Add a 'name' to each new Folio page at the very top of the file so it has a named route available for other parts of the codebase to use. diff --git a/.ai/foundation.blade.php b/.ai/foundation.blade.php index e6f68439..8c9b35a3 100644 --- a/.ai/foundation.blade.php +++ b/.ai/foundation.blade.php @@ -30,10 +30,10 @@ - Do not change the application's dependencies without approval. ## Frontend Bundling -@if ($assist->config->enforceSail) -- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->composer() }} run dev`. Ask them. +@if ($assist->config->usesSail) +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->composerCommand('run dev') }}`. Ask them. @else -- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->nodePackageManager() }} run build`, `{{ $assist->nodePackageManager() }} run dev`, or `{{ $assist->composer() }} run dev`. Ask them. +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->nodePackageManager() }} run build`, `{{ $assist->nodePackageManager() }} run dev`, or `{{ $assist->composerCommand('run dev') }}`. Ask them. @endif ## Replies diff --git a/.ai/laravel/11/core.blade.php b/.ai/laravel/11/core.blade.php index ee28b469..250c831e 100644 --- a/.ai/laravel/11/core.blade.php +++ b/.ai/laravel/11/core.blade.php @@ -1,3 +1,6 @@ +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp ## Laravel 11 - Use the `search-docs` tool to get version specific documentation. @@ -32,6 +35,6 @@ ### New Artisan Commands - List Artisan commands using Boost's MCP tool, if available. New commands available in Laravel 11: - - `{{ $assist->artisan() }} make:enum` - - `{{ $assist->artisan() }} make:class` - - `{{ $assist->artisan() }} make:interface` + - `{{ $assist->artisanCommand('make:enum') }}` + - `{{ $assist->artisanCommand('make:class') }} ` + - `{{ $assist->artisanCommand('make:interface') }} ` diff --git a/.ai/laravel/core.blade.php b/.ai/laravel/core.blade.php index ff4818ca..ef026e84 100644 --- a/.ai/laravel/core.blade.php +++ b/.ai/laravel/core.blade.php @@ -3,8 +3,8 @@ @endphp ## Do Things the Laravel Way -- Use `{{ $assist->artisan() }} make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. -- If you're creating a generic PHP class, use `{{ $assist->artisan() }} make:class`. +- Use `{{ $assist->artisanCommand('make:') }}` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. +- If you're creating a generic PHP class, use `{{ $assist->artisanCommand('make:class') }}`. - Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. ### Database @@ -15,7 +15,7 @@ - Use Laravel's query builder for very complex database operations. ### Model Creation -- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `{{ $assist->artisan() }} make:model`. +- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `{{ $assist->artisanCommand('make:model') }}`. ### APIs & Eloquent Resources - For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. @@ -39,11 +39,11 @@ ### Testing - When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. - Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. -- When creating tests, make use of `{{ $assist->artisan() }} make:test [options] ` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. +- When creating tests, make use of `{{ $assist->artisanCommand('make:test [options] ') }}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. ### Vite Error -@if ($assist->config->enforceSail) -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->composer() }} run dev` or ask the user to run it. +@if ($assist->config->usesSail) +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->composerCommand('run dev') }}` or ask the user to run it. @else -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->nodePackageManager() }} run build` or ask the user to run `{{ $assist->nodePackageManager() }} run dev` or `{{ $assist->composer() }} run dev`. +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->nodePackageManager() }} run build` or ask the user to run `{{ $assist->nodePackageManager() }} run dev` or `{{ $assist->composerCommand('run dev') }}`. @endif diff --git a/.ai/livewire/core.blade.php b/.ai/livewire/core.blade.php index 07f4c067..122cdfec 100644 --- a/.ai/livewire/core.blade.php +++ b/.ai/livewire/core.blade.php @@ -3,7 +3,7 @@ @endphp ## Livewire Core - Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. -- Use the `{{ $assist->artisan() }} make:livewire [Posts\\CreatePost]` artisan command to create new components +- Use the `{{ $assist->artisanCommand('make:livewire [Posts\\CreatePost]') }}` artisan command to create new components - State should live on the server, with the UI reflecting it. - All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. diff --git a/.ai/pest/core.blade.php b/.ai/pest/core.blade.php index 4586d325..f09f65d3 100644 --- a/.ai/pest/core.blade.php +++ b/.ai/pest/core.blade.php @@ -6,7 +6,7 @@ - If you need to verify a feature is working, write or update a Unit / Feature test. ### Pest Tests -- All tests must be written using Pest. Use `{{ $assist->artisan() }} make:test --pest `. +- All tests must be written using Pest. Use `{{ $assist->artisanCommand('make:test --pest ') }}`. - You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. - Tests should test all of the happy paths, failure paths, and weird paths. - Tests live in the `tests/Feature` and `tests/Unit` directories. @@ -19,9 +19,9 @@ ### Running Tests - Run the minimal number of tests using an appropriate filter before finalizing code edits. -- To run all tests: `{{ $assist->artisan() }} test`. -- To run all tests in a file: `{{ $assist->artisan() }} test tests/Feature/ExampleTest.php`. -- To filter on a particular test name: `{{ $assist->artisan() }} test --filter=testName` (recommended after making a change to a related file). +- To run all tests: `{{ $assist->artisanCommand('test') }}`. +- To run all tests in a file: `{{ $assist->artisanCommand('test tests/Feature/ExampleTest.php') }}`. +- To filter on a particular test name: `{{ $assist->artisanCommand('test --filter=testName') }}` (recommended after making a change to a related file). - When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. ### Pest Assertions diff --git a/.ai/phpunit/core.blade.php b/.ai/phpunit/core.blade.php index d4bab859..0761be35 100644 --- a/.ai/phpunit/core.blade.php +++ b/.ai/phpunit/core.blade.php @@ -3,7 +3,7 @@ @endphp ## PHPUnit Core -- This application uses PHPUnit for testing. All tests must be written as PHPUnit classes. Use `{{ $assist->artisan() }} make:test --phpunit ` to create a new test. +- This application uses PHPUnit for testing. All tests must be written as PHPUnit classes. Use `{{ $assist->artisanCommand('make:test --phpunit ') }}` to create a new test. - If you see a test using "Pest", convert it to PHPUnit. - Every time a test has been updated, run that singular test. - When the tests relating to your feature are passing, ask the user if they would like to also run the entire test suite to make sure everything is still passing. @@ -12,6 +12,6 @@ ### Running Tests - Run the minimal number of tests, using an appropriate filter, before finalizing. -- To run all tests: `{{ $assist->artisan() }} test`. -- To run all tests in a file: `{{ $assist->artisan() }} test tests/Feature/ExampleTest.php`. -- To filter on a particular test name: `{{ $assist->artisan() }} test --filter=testName` (recommended after making a change to a related file). +- To run all tests: `{{ $assist->artisanCommand('test') }}`. +- To run all tests in a file: `{{ $assist->artisanCommand('test tests/Feature/ExampleTest.php') }}`. +- To filter on a particular test name: `{{ $assist->artisanCommand('test --filter=testName') }}` (recommended after making a change to a related file). diff --git a/.ai/pint/core.blade.php b/.ai/pint/core.blade.php index 1e81775b..1845f783 100644 --- a/.ai/pint/core.blade.php +++ b/.ai/pint/core.blade.php @@ -3,5 +3,5 @@ @endphp ## Laravel Pint Code Formatter -- You must run `{{ $assist->bin() }}pint --dirty` before finalizing changes to ensure your code matches the project's expected style. -- Do not run `{{ $assist->bin() }}pint --test`, simply run `{{ $assist->bin() }}pint` to fix any formatting issues. +- You must run `{{ $assist->binCommand('pint') }} --dirty` before finalizing changes to ensure your code matches the project's expected style. +- Do not run `{{ $assist->binCommand('pint') }} --test`, simply run `{{ $assist->binCommand('pint') }}` to fix any formatting issues. diff --git a/.ai/sail/core.blade.php b/.ai/sail/core.blade.php index 43551993..9a9762bb 100644 --- a/.ai/sail/core.blade.php +++ b/.ai/sail/core.blade.php @@ -1,11 +1,14 @@ +@php +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +@endphp ## Laravel Sail - This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail. -- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`. -- Open the application in the browser by running `vendor/bin/sail open`. -- Always prefix PHP, Artisan, Composer, and Node commands** with `vendor/bin/sail`. Examples: - - Run migrations: `vendor/bin/sail artisan migrate` - - Install Composer packages: `vendor/bin/sail composer install` - - Run npm: `vendor/bin/sail npm run dev` - - Execute PHP scripts: `vendor/bin/sail php [script]` -- View all available Sail commands by running `vendor/bin/sail` without arguments. +- Start services using `{{$assist->sailBinaryPath()}} up -d` and stop them with `{{$assist->sailBinaryPath()}} stop`. +- Open the application in the browser by running `{{$assist->sailBinaryPath()}} open`. +- Always prefix PHP, Artisan, Composer, and Node commands** with `{{$assist->sailBinaryPath()}}`. Examples: + - Run migrations: `{{$assist->sailBinaryPath()}} artisan migrate` + - Install Composer packages: `{{$assist->sailBinaryPath()}} composer install` + - Run npm: `{{$assist->sailBinaryPath()}} npm run dev` + - Execute PHP scripts: `{{$assist->sailBinaryPath()}} php [script]` +- View all available Sail commands by running `{{$assist->sailBinaryPath()}}` without arguments. diff --git a/.ai/volt/core.blade.php b/.ai/volt/core.blade.php index 62b4a0d5..bd4028c9 100644 --- a/.ai/volt/core.blade.php +++ b/.ai/volt/core.blade.php @@ -4,7 +4,7 @@ ## Livewire Volt - This project uses Livewire Volt for interactivity within its pages. New pages requiring interactivity must also use Livewire Volt. There is documentation available for it. -- Make new Volt components using `{{ $assist->artisan() }} make:volt [name] [--test] [--pest]` +- Make new Volt components using `{{ $assist->artisanCommand('make:volt [name] [--test] [--pest]') }}` - Volt is a **class-based** and **functional** API for Livewire that supports single-file components, allowing a component's PHP logic and Blade templates to co-exist in the same file - Livewire Volt allows PHP logic and Blade templates in one file. Components use the @verbatim`@volt`@endverbatim directive. - You must check existing Volt components to determine if they're functional or class based. If you can't detect that, ask the user which they prefer before writing a Volt component. diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 02d95656..caa9f890 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -447,7 +447,7 @@ protected function installGuidelines(): void $guidelineConfig->caresAboutLocalization = $this->detectLocalization(); $guidelineConfig->hasAnApi = false; $guidelineConfig->aiGuidelines = $this->selectedAiGuidelines->values()->toArray(); - $guidelineConfig->enforceSail = $this->shouldUseSail(); + $guidelineConfig->usesSail = $this->shouldUseSail(); $composer = app(GuidelineComposer::class)->config($guidelineConfig); $guidelines = $composer->guidelines(); @@ -538,14 +538,16 @@ protected function shouldUseSail(): bool protected function buildMcpCommand(McpClient $mcpClient): array { + $serverName = 'laravel-boost'; + if ($this->shouldUseSail()) { - return ['laravel-boost', Sail::SAIL_BINARY_PATH, 'artisan', 'boost:mcp']; + return $this->sail->buildMcpCommand($serverName); } $inWsl = $this->isRunningInWsl(); return array_filter([ - 'laravel-boost', + $serverName, $inWsl ? 'wsl.exe' : false, $mcpClient->getPhpPath($inWsl), $mcpClient->getArtisanPath($inWsl), diff --git a/src/Install/GuidelineAssist.php b/src/Install/GuidelineAssist.php index afe925d9..9b94c257 100644 --- a/src/Install/GuidelineAssist.php +++ b/src/Install/GuidelineAssist.php @@ -169,24 +169,36 @@ public function nodePackageManager(): string return ($this->roster->nodePackageManager() ?? NodePackageManager::NPM)->value; } - public function artisan(): string + public function artisanCommand(string $command): string { - return $this->config->enforceSail - ? Sail::SAIL_BINARY_PATH.' artisan' - : 'php artisan'; + return "{$this->artisan()} {$command}"; } - public function composer(): string + public function composerCommand(string $command): string { - return $this->config->enforceSail - ? Sail::SAIL_BINARY_PATH.' composer' + $composerCommand = $this->config->usesSail + ? Sail::composerCommand() : 'composer'; + + return "{$composerCommand} {$command}"; + } + + public function binCommand(string $command): string + { + return $this->config->usesSail + ? Sail::binCommand().$command + : "vendor/bin/{$command}"; + } + + public function artisan(): string + { + return $this->config->usesSail + ? Sail::artisanCommand() + : 'php artisan'; } - public function bin(): string + public function sailBinaryPath(): string { - return $this->config->enforceSail - ? Sail::SAIL_BINARY_PATH.' bin ' - : 'vendor/bin/'; + return Sail::BINARY_PATH; } } diff --git a/src/Install/GuidelineComposer.php b/src/Install/GuidelineComposer.php index bd7ab9b1..78c9936a 100644 --- a/src/Install/GuidelineComposer.php +++ b/src/Install/GuidelineComposer.php @@ -132,11 +132,11 @@ protected function find(): Collection // $phpMajorMinor = PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION; // $guidelines->put('php/v'.$phpMajorMinor, $this->guidelinesDir('php/'.$phpMajorMinor)); - if (str_contains((string) config('app.url'), '.test') && $this->herd->isInstalled() && ! $this->config->enforceSail) { + if (str_contains((string) config('app.url'), '.test') && $this->herd->isInstalled() && ! $this->config->usesSail) { $guidelines->put('herd', $this->guideline('herd/core')); } - if ($this->config->enforceSail) { + if ($this->config->usesSail) { $guidelines->put('sail', $this->guideline('sail/core')); } diff --git a/src/Install/GuidelineConfig.php b/src/Install/GuidelineConfig.php index fa0b1cb7..45fbe429 100644 --- a/src/Install/GuidelineConfig.php +++ b/src/Install/GuidelineConfig.php @@ -10,7 +10,7 @@ class GuidelineConfig public bool $laravelStyle = false; - public bool $enforceSail = false; + public bool $usesSail = false; public bool $caresAboutLocalization = false; diff --git a/src/Install/Sail.php b/src/Install/Sail.php index a2e7b5e8..2030ade2 100644 --- a/src/Install/Sail.php +++ b/src/Install/Sail.php @@ -8,16 +8,39 @@ class Sail { - public const SAIL_BINARY_PATH = 'vendor'.DIRECTORY_SEPARATOR.'bin'.DIRECTORY_SEPARATOR.'sail'; + public const BINARY_PATH = 'vendor'.DIRECTORY_SEPARATOR.'bin'.DIRECTORY_SEPARATOR.'sail'; + + public static function artisanCommand(): string + { + return self::BINARY_PATH.' artisan'; + } + + public static function binCommand(): string + { + return self::BINARY_PATH.' bin '; + } + + public static function composerCommand(): string + { + return self::BINARY_PATH.' composer'; + } public function isInstalled(): bool { - return file_exists(base_path(self::SAIL_BINARY_PATH)) && - (file_exists(base_path('docker-compose.yml')) || file_exists(base_path('compose.yaml'))); + return file_exists(base_path(self::BINARY_PATH)) && + (file_exists(base_path('docker-compose.yml')) || file_exists(base_path('compose.yaml'))); } public function isActive(): bool { return get_current_user() === 'sail' || getenv('LARAVEL_SAIL') === '1'; } + + /** + * @return array + */ + public function buildMcpCommand(string $serverName): array + { + return [$serverName, self::BINARY_PATH, 'artisan', 'boost:mcp']; + } } diff --git a/tests/Feature/Install/GuidelineComposerTest.php b/tests/Feature/Install/GuidelineComposerTest.php index d9e2a608..91fa4171 100644 --- a/tests/Feature/Install/GuidelineComposerTest.php +++ b/tests/Feature/Install/GuidelineComposerTest.php @@ -164,7 +164,7 @@ config(['app.url' => 'http://myapp.test']); $config = new GuidelineConfig; - $config->enforceSail = true; + $config->usesSail = true; $guidelines = $this->composer ->config($config) @@ -187,7 +187,7 @@ config(['app.url' => 'http://myapp.test']); $config = new GuidelineConfig; - $config->enforceSail = false; + $config->usesSail = false; $guidelines = $this->composer ->config($config) @@ -629,7 +629,7 @@ expect($guidelines) ->toContain('=== wayfinder/core rules ===') ->toContain('## Laravel Wayfinder') - ->toContain('import { show } from \'@/actions/') + ->toContain("import { show, store, update } from '@/actions/App/Http/Controllers/PostController'") ->not->toContain('Wayfinder + Inertia') ->not->toContain('Wayfinder Form Component'); }); From 0d187daa4bc40fa797234a498709655dd1472c79 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Fri, 7 Nov 2025 18:48:04 +0530 Subject: [PATCH 08/21] Refactor shouldUseSail method to handle empty selectedBoostFeatures case Signed-off-by: Pushpak Chhajed --- src/Console/InstallCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index caa9f890..6cd9fe02 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -533,6 +533,10 @@ protected function shouldInstallHerdMcp(): bool protected function shouldUseSail(): bool { + if ($this->selectedBoostFeatures->isEmpty()) { + return $this->config->getSail(); + } + return $this->selectedBoostFeatures->contains('sail'); } From 3aa2c8d2f43fba18ceb1df14d2078d67c2592872 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Fri, 7 Nov 2025 19:15:09 +0530 Subject: [PATCH 09/21] More controlled guideliens Signed-off-by: Pushpak Chhajed --- .ai/foundation.blade.php | 6 +----- .ai/laravel/core.blade.php | 6 +----- .ai/sail/core.blade.php | 6 +++--- src/Install/GuidelineAssist.php | 9 +++++++-- src/Install/Sail.php | 5 +++++ .../.ai/guidelines/test-blade-with-assist.blade.php | 4 ++-- 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.ai/foundation.blade.php b/.ai/foundation.blade.php index 8c9b35a3..9f855c64 100644 --- a/.ai/foundation.blade.php +++ b/.ai/foundation.blade.php @@ -30,11 +30,7 @@ - Do not change the application's dependencies without approval. ## Frontend Bundling -@if ($assist->config->usesSail) -- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->composerCommand('run dev') }}`. Ask them. -@else -- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->nodePackageManager() }} run build`, `{{ $assist->nodePackageManager() }} run dev`, or `{{ $assist->composerCommand('run dev') }}`. Ask them. -@endif +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->nodePackageManager('run build') }}`, `{{ $assist->nodePackageManager('run dev') }}`, or `{{ $assist->composerCommand('run dev') }}`. Ask them. ## Replies - Be concise in your explanations - focus on what's important rather than explaining obvious details. diff --git a/.ai/laravel/core.blade.php b/.ai/laravel/core.blade.php index ef026e84..a07dbd55 100644 --- a/.ai/laravel/core.blade.php +++ b/.ai/laravel/core.blade.php @@ -42,8 +42,4 @@ - When creating tests, make use of `{{ $assist->artisanCommand('make:test [options] ') }}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. ### Vite Error -@if ($assist->config->usesSail) -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->composerCommand('run dev') }}` or ask the user to run it. -@else -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->nodePackageManager() }} run build` or ask the user to run `{{ $assist->nodePackageManager() }} run dev` or `{{ $assist->composerCommand('run dev') }}`. -@endif +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->nodePackageManager('run build') }}` or ask the user to run `{{ $assist->nodePackageManager('run dev') }}` or `{{ $assist->composerCommand('run dev') }}`. diff --git a/.ai/sail/core.blade.php b/.ai/sail/core.blade.php index 9a9762bb..ded6e241 100644 --- a/.ai/sail/core.blade.php +++ b/.ai/sail/core.blade.php @@ -7,8 +7,8 @@ - Start services using `{{$assist->sailBinaryPath()}} up -d` and stop them with `{{$assist->sailBinaryPath()}} stop`. - Open the application in the browser by running `{{$assist->sailBinaryPath()}} open`. - Always prefix PHP, Artisan, Composer, and Node commands** with `{{$assist->sailBinaryPath()}}`. Examples: - - Run migrations: `{{$assist->sailBinaryPath()}} artisan migrate` - - Install Composer packages: `{{$assist->sailBinaryPath()}} composer install` - - Run npm: `{{$assist->sailBinaryPath()}} npm run dev` + - Run Artisan Commands: `{{$assist->artisanCommand('migrate')}}` + - Install Composer packages: `{{$assist->composerCommand('install')}}` + - Execute node commands: `{{$assist->nodePackageManager('run dev')}}` - Execute PHP scripts: `{{$assist->sailBinaryPath()}} php [script]` - View all available Sail commands by running `{{$assist->sailBinaryPath()}}` without arguments. diff --git a/src/Install/GuidelineAssist.php b/src/Install/GuidelineAssist.php index 9b94c257..e9cc81b3 100644 --- a/src/Install/GuidelineAssist.php +++ b/src/Install/GuidelineAssist.php @@ -164,9 +164,14 @@ public function inertia(): Inertia return new Inertia($this->roster); } - public function nodePackageManager(): string + public function nodePackageManager(string $command): string { - return ($this->roster->nodePackageManager() ?? NodePackageManager::NPM)->value; + $manager = ($this->roster->nodePackageManager() ?? NodePackageManager::NPM)->value; + $nodePackageManagerCommand = $this->config->usesSail + ? Sail::nodePackageManagerCommand($manager) + : $manager; + + return "{$nodePackageManagerCommand} {$command}"; } public function artisanCommand(string $command): string diff --git a/src/Install/Sail.php b/src/Install/Sail.php index 2030ade2..7e909862 100644 --- a/src/Install/Sail.php +++ b/src/Install/Sail.php @@ -25,6 +25,11 @@ public static function composerCommand(): string return self::BINARY_PATH.' composer'; } + public static function nodePackageManagerCommand(string $manager): string + { + return self::BINARY_PATH.' '.$manager; + } + public function isInstalled(): bool { return file_exists(base_path(self::BINARY_PATH)) && diff --git a/tests/fixtures/.ai/guidelines/test-blade-with-assist.blade.php b/tests/fixtures/.ai/guidelines/test-blade-with-assist.blade.php index 986b1ee3..09d82c1f 100644 --- a/tests/fixtures/.ai/guidelines/test-blade-with-assist.blade.php +++ b/tests/fixtures/.ai/guidelines/test-blade-with-assist.blade.php @@ -1,5 +1,5 @@ # Blade with @assist variable -Run `{{ $assist->nodePackageManager() }} install` to install dependencies. +Run `{{ $assist->nodePackageManager('install') }}` to install dependencies. -Package manager: {{ $assist->nodePackageManager() }} +Package manager: {{ $assist->nodePackageManager('install') }} From 7f47c2e772f75443b70de0324a358af8cd117b28 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Fri, 7 Nov 2025 19:51:26 +0530 Subject: [PATCH 10/21] Refactor command name Signed-off-by: Pushpak Chhajed --- .ai/foundation.blade.php | 8 ++++---- .ai/laravel/core.blade.php | 2 +- .ai/sail/core.blade.php | 8 ++++---- src/Install/GuidelineAssist.php | 2 +- tests/Feature/Install/GuidelineComposerTest.php | 2 +- .../.ai/guidelines/test-blade-with-assist.blade.php | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.ai/foundation.blade.php b/.ai/foundation.blade.php index 9f855c64..3ed0211c 100644 --- a/.ai/foundation.blade.php +++ b/.ai/foundation.blade.php @@ -1,5 +1,5 @@ @php -/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ + /** @var \Laravel\Boost\Install\GuidelineAssist $assist */ @endphp # Laravel Boost Guidelines @@ -10,11 +10,11 @@ - php - {{ PHP_VERSION }} @foreach (app(\Laravel\Roster\Roster::class)->packages()->unique(fn ($package) => $package->rawName()) as $package) -- {{ $package->rawName() }} ({{ $package->name() }}) - v{{ $package->majorVersion() }} + - {{ $package->rawName() }} ({{ $package->name() }}) - v{{ $package->majorVersion() }} @endforeach @if (! empty(config('boost.purpose'))) -Application purpose: {!! config('boost.purpose') !!} + Application purpose: {!! config('boost.purpose') !!} @endif ## Conventions @@ -30,7 +30,7 @@ - Do not change the application's dependencies without approval. ## Frontend Bundling -- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->nodePackageManager('run build') }}`, `{{ $assist->nodePackageManager('run dev') }}`, or `{{ $assist->composerCommand('run dev') }}`. Ask them. +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `{{ $assist->nodePackageManagerCommand('run build') }}`, `{{ $assist->nodePackageManagerCommand('run dev') }}`, or `{{ $assist->composerCommand('run dev') }}`. Ask them. ## Replies - Be concise in your explanations - focus on what's important rather than explaining obvious details. diff --git a/.ai/laravel/core.blade.php b/.ai/laravel/core.blade.php index a07dbd55..a339e38b 100644 --- a/.ai/laravel/core.blade.php +++ b/.ai/laravel/core.blade.php @@ -42,4 +42,4 @@ - When creating tests, make use of `{{ $assist->artisanCommand('make:test [options] ') }}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. ### Vite Error -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->nodePackageManager('run build') }}` or ask the user to run `{{ $assist->nodePackageManager('run dev') }}` or `{{ $assist->composerCommand('run dev') }}`. +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->nodePackageManagerCommand('run build') }}` or ask the user to run `{{ $assist->nodePackageManagerCommand('run dev') }}` or `{{ $assist->composerCommand('run dev') }}`. diff --git a/.ai/sail/core.blade.php b/.ai/sail/core.blade.php index ded6e241..88ee8d64 100644 --- a/.ai/sail/core.blade.php +++ b/.ai/sail/core.blade.php @@ -7,8 +7,8 @@ - Start services using `{{$assist->sailBinaryPath()}} up -d` and stop them with `{{$assist->sailBinaryPath()}} stop`. - Open the application in the browser by running `{{$assist->sailBinaryPath()}} open`. - Always prefix PHP, Artisan, Composer, and Node commands** with `{{$assist->sailBinaryPath()}}`. Examples: - - Run Artisan Commands: `{{$assist->artisanCommand('migrate')}}` - - Install Composer packages: `{{$assist->composerCommand('install')}}` - - Execute node commands: `{{$assist->nodePackageManager('run dev')}}` - - Execute PHP scripts: `{{$assist->sailBinaryPath()}} php [script]` +- Run Artisan Commands: `{{$assist->artisanCommand('migrate')}}` +- Install Composer packages: `{{$assist->composerCommand('install')}}` +- Execute node commands: `{{$assist->nodePackageManagerCommand('run dev')}}` +- Execute PHP scripts: `{{$assist->sailBinaryPath()}} php [script]` - View all available Sail commands by running `{{$assist->sailBinaryPath()}}` without arguments. diff --git a/src/Install/GuidelineAssist.php b/src/Install/GuidelineAssist.php index e9cc81b3..ec43cd59 100644 --- a/src/Install/GuidelineAssist.php +++ b/src/Install/GuidelineAssist.php @@ -164,7 +164,7 @@ public function inertia(): Inertia return new Inertia($this->roster); } - public function nodePackageManager(string $command): string + public function nodePackageManagerCommand(string $command): string { $manager = ($this->roster->nodePackageManager() ?? NodePackageManager::NPM)->value; $nodePackageManagerCommand = $this->config->usesSail diff --git a/tests/Feature/Install/GuidelineComposerTest.php b/tests/Feature/Install/GuidelineComposerTest.php index 91fa4171..6648682f 100644 --- a/tests/Feature/Install/GuidelineComposerTest.php +++ b/tests/Feature/Install/GuidelineComposerTest.php @@ -468,7 +468,7 @@ // Processes blade variables correctly ->toContain('=== .ai/test-blade-with-assist rules ===') ->toContain('Run `npm install` to install dependencies') - ->toContain('Package manager: npm') + ->toContain('Package manager: npm install') // Preserves @volt directives in blade templates ->toContain('`@volt`') ->toContain('@endvolt') diff --git a/tests/fixtures/.ai/guidelines/test-blade-with-assist.blade.php b/tests/fixtures/.ai/guidelines/test-blade-with-assist.blade.php index 09d82c1f..48ce0456 100644 --- a/tests/fixtures/.ai/guidelines/test-blade-with-assist.blade.php +++ b/tests/fixtures/.ai/guidelines/test-blade-with-assist.blade.php @@ -1,5 +1,5 @@ # Blade with @assist variable -Run `{{ $assist->nodePackageManager('install') }}` to install dependencies. +Run `{{ $assist->nodePackageManagerCommand('install') }}` to install dependencies. -Package manager: {{ $assist->nodePackageManager('install') }} +Package manager: {{ $assist->nodePackageManagerCommand('install') }} From c5db2f4b6938cc80ba5fbaa0fa4b8ae3826a323f Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Fri, 7 Nov 2025 20:49:38 +0530 Subject: [PATCH 11/21] Refactor core.blade.php Signed-off-by: Pushpak Chhajed --- .ai/pest/core.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ai/pest/core.blade.php b/.ai/pest/core.blade.php index f09f65d3..a5a0bd09 100644 --- a/.ai/pest/core.blade.php +++ b/.ai/pest/core.blade.php @@ -6,7 +6,7 @@ - If you need to verify a feature is working, write or update a Unit / Feature test. ### Pest Tests -- All tests must be written using Pest. Use `{{ $assist->artisanCommand('make:test --pest ') }}`. +- All tests must be written using Pest. Use `{{ $assist->artisanCommand('make:test --pest {name}') }}`. - You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. - Tests should test all of the happy paths, failure paths, and weird paths. - Tests live in the `tests/Feature` and `tests/Unit` directories. From 2935473c93e1751462f039aecbade1ccaf3368ed Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Fri, 7 Nov 2025 21:02:27 +0530 Subject: [PATCH 12/21] Refactor core.blade.php Signed-off-by: Pushpak Chhajed --- .ai/folio/core.blade.php | 4 ++-- .ai/laravel/core.blade.php | 2 +- .ai/phpunit/core.blade.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.ai/folio/core.blade.php b/.ai/folio/core.blade.php index b238fd96..b022185a 100644 --- a/.ai/folio/core.blade.php +++ b/.ai/folio/core.blade.php @@ -14,10 +14,10 @@ // Creates: resources/views/pages/products.blade.php → /products - {{ $assist->artisanCommand("folio:page 'products'") }} + {!! $assist->artisanCommand('folio:page "products"') !!} // Creates: resources/views/pages/products/[id].blade.php → /products/{id} - {{ $assist->artisanCommand("folio:page 'products/[id]'") }} + {!! $assist->artisanCommand('folio:page "products/[id]"') !!} - Add a 'name' to each new Folio page at the very top of the file so it has a named route available for other parts of the codebase to use. diff --git a/.ai/laravel/core.blade.php b/.ai/laravel/core.blade.php index a339e38b..74798cea 100644 --- a/.ai/laravel/core.blade.php +++ b/.ai/laravel/core.blade.php @@ -39,7 +39,7 @@ ### Testing - When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. - Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. -- When creating tests, make use of `{{ $assist->artisanCommand('make:test [options] ') }}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. +- When creating tests, make use of `{{ $assist->artisanCommand('make:test [options] {name}') }}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. ### Vite Error - If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `{{ $assist->nodePackageManagerCommand('run build') }}` or ask the user to run `{{ $assist->nodePackageManagerCommand('run dev') }}` or `{{ $assist->composerCommand('run dev') }}`. diff --git a/.ai/phpunit/core.blade.php b/.ai/phpunit/core.blade.php index 0761be35..48e2197b 100644 --- a/.ai/phpunit/core.blade.php +++ b/.ai/phpunit/core.blade.php @@ -3,7 +3,7 @@ @endphp ## PHPUnit Core -- This application uses PHPUnit for testing. All tests must be written as PHPUnit classes. Use `{{ $assist->artisanCommand('make:test --phpunit ') }}` to create a new test. +- This application uses PHPUnit for testing. All tests must be written as PHPUnit classes. Use `{{ $assist->artisanCommand('make:test --phpunit {name}') }}` to create a new test. - If you see a test using "Pest", convert it to PHPUnit. - Every time a test has been updated, run that singular test. - When the tests relating to your feature are passing, ask the user if they would like to also run the entire test suite to make sure everything is still passing. From 07164b120aa728b839d4f2ef04a253b8a388a2cf Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Fri, 7 Nov 2025 21:03:35 +0530 Subject: [PATCH 13/21] Formatting Signed-off-by: Pushpak Chhajed --- .ai/foundation.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ai/foundation.blade.php b/.ai/foundation.blade.php index 3ed0211c..1663ab2c 100644 --- a/.ai/foundation.blade.php +++ b/.ai/foundation.blade.php @@ -1,5 +1,5 @@ @php - /** @var \Laravel\Boost\Install\GuidelineAssist $assist */ +/** @var \Laravel\Boost\Install\GuidelineAssist $assist */ @endphp # Laravel Boost Guidelines From da8a16d22ddf727ff74b27e5584a703cb1c27499 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Fri, 7 Nov 2025 21:04:43 +0530 Subject: [PATCH 14/21] Formatting Signed-off-by: Pushpak Chhajed --- .ai/foundation.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ai/foundation.blade.php b/.ai/foundation.blade.php index 1663ab2c..efce9a42 100644 --- a/.ai/foundation.blade.php +++ b/.ai/foundation.blade.php @@ -10,7 +10,7 @@ - php - {{ PHP_VERSION }} @foreach (app(\Laravel\Roster\Roster::class)->packages()->unique(fn ($package) => $package->rawName()) as $package) - - {{ $package->rawName() }} ({{ $package->name() }}) - v{{ $package->majorVersion() }} +- {{ $package->rawName() }} ({{ $package->name() }}) - v{{ $package->majorVersion() }} @endforeach @if (! empty(config('boost.purpose'))) From c1c5e19810aa97a044dcc98f4e93fc40b13dd7bb Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Fri, 7 Nov 2025 21:05:20 +0530 Subject: [PATCH 15/21] Formatting Signed-off-by: Pushpak Chhajed --- .ai/foundation.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ai/foundation.blade.php b/.ai/foundation.blade.php index efce9a42..8e11a2e1 100644 --- a/.ai/foundation.blade.php +++ b/.ai/foundation.blade.php @@ -14,7 +14,7 @@ @endforeach @if (! empty(config('boost.purpose'))) - Application purpose: {!! config('boost.purpose') !!} +Application purpose: {!! config('boost.purpose') !!} @endif ## Conventions From a5b1d69dccb7ae7940b80b8a7f486d85c4902625 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Fri, 7 Nov 2025 21:09:42 +0530 Subject: [PATCH 16/21] Add nodePackageManager for backward support Signed-off-by: Pushpak Chhajed --- src/Install/GuidelineAssist.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Install/GuidelineAssist.php b/src/Install/GuidelineAssist.php index ec43cd59..8c869740 100644 --- a/src/Install/GuidelineAssist.php +++ b/src/Install/GuidelineAssist.php @@ -164,9 +164,14 @@ public function inertia(): Inertia return new Inertia($this->roster); } + public function nodePackageManager(): string + { + return ($this->roster->nodePackageManager() ?? NodePackageManager::NPM)->value; + } + public function nodePackageManagerCommand(string $command): string { - $manager = ($this->roster->nodePackageManager() ?? NodePackageManager::NPM)->value; + $manager = $this->nodePackageManager(); $nodePackageManagerCommand = $this->config->usesSail ? Sail::nodePackageManagerCommand($manager) : $manager; From 8d1586b2d967e823cabf29139a4b2722dca04a6b Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 13 Nov 2025 15:43:53 -0500 Subject: [PATCH 17/21] single quotes for consistency --- .ai/folio/core.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ai/folio/core.blade.php b/.ai/folio/core.blade.php index b022185a..c4ccd2e6 100644 --- a/.ai/folio/core.blade.php +++ b/.ai/folio/core.blade.php @@ -7,7 +7,7 @@ - `pages/index.blade.php` → `/` - `pages/profile/index.blade.php` → `/profile` - `pages/auth/login.blade.php` → `/auth/login` -- You may list available Folio routes using `{{ $assist->artisanCommand("folio:list") }}` or using Boost's `list-routes` tool. +- You may list available Folio routes using `{{ $assist->artisanCommand('folio:list') }}` or using Boost's `list-routes` tool. ### New Pages & Routes - Always create new `folio` pages and routes using `{{ $assist->artisanCommand('folio:page [name]') }}` following existing naming conventions. From 3851bf1e899e2575eb715f813d9be5a5e4c7477d Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 13 Nov 2025 15:44:31 -0500 Subject: [PATCH 18/21] spaces around brackets for consistency --- .ai/sail/core.blade.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.ai/sail/core.blade.php b/.ai/sail/core.blade.php index 88ee8d64..8e3c43e3 100644 --- a/.ai/sail/core.blade.php +++ b/.ai/sail/core.blade.php @@ -4,11 +4,11 @@ ## Laravel Sail - This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail. -- Start services using `{{$assist->sailBinaryPath()}} up -d` and stop them with `{{$assist->sailBinaryPath()}} stop`. -- Open the application in the browser by running `{{$assist->sailBinaryPath()}} open`. -- Always prefix PHP, Artisan, Composer, and Node commands** with `{{$assist->sailBinaryPath()}}`. Examples: -- Run Artisan Commands: `{{$assist->artisanCommand('migrate')}}` -- Install Composer packages: `{{$assist->composerCommand('install')}}` -- Execute node commands: `{{$assist->nodePackageManagerCommand('run dev')}}` -- Execute PHP scripts: `{{$assist->sailBinaryPath()}} php [script]` -- View all available Sail commands by running `{{$assist->sailBinaryPath()}}` without arguments. +- Start services using `{{ $assist->sailBinaryPath() }} up -d` and stop them with `{{ $assist->sailBinaryPath() }} stop`. +- Open the application in the browser by running `{{ $assist->sailBinaryPath() }} open`. +- Always prefix PHP, Artisan, Composer, and Node commands** with `{{ $assist->sailBinaryPath() }}`. Examples: +- Run Artisan Commands: `{{ $assist->artisanCommand('migrate') }}` +- Install Composer packages: `{{ $assist->composerCommand('install') }}` +- Execute node commands: `{{ $assist->nodePackageManagerCommand('run dev') }}` +- Execute PHP scripts: `{{ $assist->sailBinaryPath() }} php [script]` +- View all available Sail commands by running `{{ $assist->sailBinaryPath() }}` without arguments. From 8a2d4db08ec84e3606fe70b7f5070cf6e384e7b5 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 13 Nov 2025 15:45:15 -0500 Subject: [PATCH 19/21] spaces around brackets for consistency --- .ai/wayfinder/core.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ai/wayfinder/core.blade.php b/.ai/wayfinder/core.blade.php index 2b1f0d03..a4769ada 100644 --- a/.ai/wayfinder/core.blade.php +++ b/.ai/wayfinder/core.blade.php @@ -9,7 +9,7 @@ - Always use `search-docs` to check wayfinder correct usage before implementing any features. - Always Prefer named imports for tree-shaking (e.g., `import { show } from '@/actions/...'`) - Avoid default controller imports (prevents tree-shaking) -- Run `{{$assist->artisanCommand('wayfinder:generate')}}` after route changes if Vite plugin isn't installed +- Run `{{ $assist->artisanCommand('wayfinder:generate') }}` after route changes if Vite plugin isn't installed ### Feature Overview - Form Support: Use `.form()` with `--with-form` flag for HTML form attributes — `
` → `action="/posts" method="post"` From 0244ac19f5a1cf4aa8e6720f56bf6773801ab8a1 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 13 Nov 2025 15:50:47 -0500 Subject: [PATCH 20/21] extract commands to `command` method --- src/Install/Sail.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Install/Sail.php b/src/Install/Sail.php index 7e909862..a07b46bb 100644 --- a/src/Install/Sail.php +++ b/src/Install/Sail.php @@ -12,22 +12,27 @@ class Sail public static function artisanCommand(): string { - return self::BINARY_PATH.' artisan'; + return self::command('artisan'); } public static function binCommand(): string { - return self::BINARY_PATH.' bin '; + return self::command('bin '); } public static function composerCommand(): string { - return self::BINARY_PATH.' composer'; + return self::command('composer'); } public static function nodePackageManagerCommand(string $manager): string { - return self::BINARY_PATH.' '.$manager; + return self::command($manager); + } + + public static function command(string $command): string + { + return self::BINARY_PATH.' '.$command; } public function isInstalled(): bool From 4fe14c7f0b320554bd4be54c60549c40e13b91f5 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 13 Nov 2025 15:58:17 -0500 Subject: [PATCH 21/21] wip --- src/Install/GuidelineComposer.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Install/GuidelineComposer.php b/src/Install/GuidelineComposer.php index 78c9936a..3056466d 100644 --- a/src/Install/GuidelineComposer.php +++ b/src/Install/GuidelineComposer.php @@ -227,12 +227,8 @@ protected function shouldExcludePackage(Package $package): bool foreach ($this->packagePriorities as $priorityPackage => $excludedPackages) { $packageIsInExclusionList = in_array($package->package()->value, $excludedPackages, true); - if ($packageIsInExclusionList) { - $priorityPackageExists = $this->roster->uses(Packages::from($priorityPackage)); - - if ($priorityPackageExists) { - return true; - } + if ($packageIsInExclusionList && $this->roster->uses(Packages::from($priorityPackage))) { + return true; } }