Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/laravel-best-practices/rules/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Correct:

## CSRF Protection

Include `@csrf` in all POST/PUT/DELETE Blade forms. In Inertia apps, the `@csrf` directive is automatically applied.
Include `@csrf` in all POST/PUT/DELETE Blade forms. Inertia doesn't use `@csrf`; its HTTP client sends the `XSRF-TOKEN` cookie back as the `X-XSRF-TOKEN` header, which Laravel accepts in place of the `_token` field.

Incorrect:
```blade
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/laravel-best-practices/rules/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Correct:

## CSRF Protection

Include `@csrf` in all POST/PUT/DELETE Blade forms. In Inertia apps, the `@csrf` directive is automatically applied.
Include `@csrf` in all POST/PUT/DELETE Blade forms. Inertia doesn't use `@csrf`; its HTTP client sends the `XSRF-TOKEN` cookie back as the `X-XSRF-TOKEN` header, which Laravel accepts in place of the `_token` field.

Incorrect:
```blade
Expand Down
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database


# PHP_CLI_SERVER_WORKERS=4

BCRYPT_ROUNDS=12
Expand Down
2 changes: 1 addition & 1 deletion .github/skills/laravel-best-practices/rules/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Correct:

## CSRF Protection

Include `@csrf` in all POST/PUT/DELETE Blade forms. In Inertia apps, the `@csrf` directive is automatically applied.
Include `@csrf` in all POST/PUT/DELETE Blade forms. Inertia doesn't use `@csrf`; its HTTP client sends the `XSRF-TOKEN` cookie back as the `X-XSRF-TOKEN` header, which Laravel accepts in place of the `_token` field.

Incorrect:
```blade
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ This project has domain-specific skills available in `**/skills/**`. You MUST ac

## Project Rules

- This project keeps committed, area-grouped rules in `.ai/rules` (settled decisions, non-obvious traps, standing constraints). Framework and package guidelines that only apply to specific paths (testing, frontend, components) also live there, under `.ai/rules/boost` — this is not just recorded decisions, it is load-bearing guidance you have not seen inline. Before you enter plan mode or create/edit any file, you MUST first: open @.ai/rules/index.md (it maps file globs to rule files), read every rule file whose globs cover the path(s) in scope, and run `grep -rin 'keyword' .ai/rules` to catch what a path match alone misses. Do not write code until you have read and are following every matching rule.
- This project contains committed, area-grouped rules in `.ai/rules` when that directory exists (settled decisions, non-obvious traps, standing constraints). Framework and package guidelines that only apply to specific paths (testing, frontend, components) also live there, under `.ai/rules/boost` — this is not just recorded decisions, it is load-bearing guidance you have not seen inline. Before you enter plan mode or create/edit any file, you MUST first: open @.ai/rules/index.md (it maps file globs to rule files), read every rule file whose globs cover the path(s) in scope, and run `grep -rin 'keyword' .ai/rules` to catch what a path match alone misses. Do not write code until you have read and are following every matching rule. If `.ai/rules` does not exist, continue without it.
- Record durable rules with `record-rule` so the next agent or teammate inherits them instead of working them out again. Pass a `glob` (e.g. `app/Http/Controllers/**`), a short `title`, and a few-line `note`. Always use `record-rule`, never your native memory or notes tool — native memory is personal and session-scoped; only `.ai/rules` is shared with the team and persists in the repo.

## Artisan
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ This project has domain-specific skills available in `**/skills/**`. You MUST ac

## Project Rules

- This project keeps committed, area-grouped rules in `.ai/rules` (settled decisions, non-obvious traps, standing constraints). Framework and package guidelines that only apply to specific paths (testing, frontend, components) also live there, under `.ai/rules/boost` — this is not just recorded decisions, it is load-bearing guidance you have not seen inline. Before you enter plan mode or create/edit any file, you MUST first: open @.ai/rules/index.md (it maps file globs to rule files), read every rule file whose globs cover the path(s) in scope, and run `grep -rin 'keyword' .ai/rules` to catch what a path match alone misses. Do not write code until you have read and are following every matching rule.
- This project contains committed, area-grouped rules in `.ai/rules` when that directory exists (settled decisions, non-obvious traps, standing constraints). Framework and package guidelines that only apply to specific paths (testing, frontend, components) also live there, under `.ai/rules/boost` — this is not just recorded decisions, it is load-bearing guidance you have not seen inline. Before you enter plan mode or create/edit any file, you MUST first: open @.ai/rules/index.md (it maps file globs to rule files), read every rule file whose globs cover the path(s) in scope, and run `grep -rin 'keyword' .ai/rules` to catch what a path match alone misses. Do not write code until you have read and are following every matching rule. If `.ai/rules` does not exist, continue without it.
- Record durable rules with `record-rule` so the next agent or teammate inherits them instead of working them out again. Pass a `glob` (e.g. `app/Http/Controllers/**`), a short `title`, and a few-line `note`. Always use `record-rule`, never your native memory or notes tool — native memory is personal and session-scoped; only `.ai/rules` is shared with the team and persists in the repo.

## Artisan
Expand Down
41 changes: 37 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,48 @@
# Get Started
# Summit

Getting started with Summit is super easy. Just start a new Laravel project, and use the Summit repo as your starter kit
Summit is an opinionated Laravel Vue starter kit.

## Create a project

Create a fresh application with the Laravel installer:

```bash
laravel new my-project --using=zacksmash/summit
```

Then, `cd` into `my-project` and run
The installer creates the environment file and SQLite database, generates the application key and Passport keys, runs the migrations, and launches Chisel. Chisel lets you choose authentication features, teams, Passport, application MCP scaffolding, Octane, browser testing, AI tooling, IDE Helper, Whisky, and Herd integration. It removes everything you deselect and rebuilds the database schema to match. The feature selection needs an interactive terminal; in a non-interactive session it is skipped, and you can run it later with `php artisan install:features`.

Then start local development:

```bash
cd my-project
composer dev
```

If you pass `--no-node` to `laravel new`, install and build the frontend separately with `npm install && npm run build`.

## Set up a cloned repository

For a direct Git clone instead of a Laravel installer project, run:

```bash
composer setup
```

Now, you're all setup with Laravel Octane over HTTPS! Just run `composer dev` for local development and happy coding!
This installs the dependencies, creates the environment file and keys, migrates and seeds the database, installs the Playwright browsers, and builds the frontend — without requiring Herd or resetting an existing database. It is safe to run again. The first dependency install also launches the Chisel feature selection when a terminal is attached.

## Optional local tooling

Install the FrankenPHP runtime for Octane on macOS, Linux, or Windows via WSL:

```bash
composer setup:octane
```

Run the complete opinionated tooling setup, including Octane, Whisky, IDE Helper, Playwright, and Laravel Herd HTTPS proxying:

```bash
composer setup:tools
```

The tooling setup requires Laravel Herd and a POSIX shell (macOS, Linux, or Windows via WSL). It initializes a Git repository when needed, installs the Git hooks, and creates the generated baseline with a one-time `--no-verify` commit after setup succeeds. Later commits run Whisky normally. Without an installed Octane runtime, `composer dev` automatically uses Laravel's built-in development server.
6 changes: 6 additions & 0 deletions app/Actions/Fortify/CreateNewUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@

namespace App\Actions\Fortify;

/* @chisel-teams */
use App\Actions\Teams\CreateTeam;
/* @end-chisel-teams */
use App\Concerns\PasswordValidationRules;
use App\Concerns\ProfileValidationRules;
use App\Models\User;
/* @chisel-teams */
use Illuminate\Support\Facades\DB;
/* @end-chisel-teams */
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\CreatesNewUsers;

class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules, ProfileValidationRules;

/* @chisel-teams */
public function __construct(private CreateTeam $createTeam)
{
//
}
/* @end-chisel-teams */

/**
* Validate and create a newly registered user.
Expand Down
117 changes: 117 additions & 0 deletions app/Console/Commands/InstallFeaturesCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

namespace App\Console\Commands;

use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Support\Env;
use Illuminate\Support\Facades\Request;
use Laravel\Chisel\Chisel;
use Laravel\Chisel\Question;
use Laravel\Chisel\Script;

use function Laravel\Prompts\multiselect;
use function Laravel\Prompts\spin;

#[Description('Choose which starter kit features to keep')]
#[Signature('install:features
{--answers= : JSON string of answers to skip interactive prompts}')]
class InstallFeaturesCommand extends Command
{
public function handle(): int
{
if ($this->shouldDeferInstallerHooks()) {
return self::SUCCESS;
}

if (! file_exists(base_path('chisel.php'))) {
return self::SUCCESS;
}

if ($this->option('answers') === null && ! $this->input->isInteractive()) {
$this->components->warn(
'Skipping starter kit feature selection because the session is not interactive.'
.' Run [php artisan install:features] from a terminal, or pass [--answers] to select features without prompts.',
);

return self::SUCCESS;
}

/** @var Script $script */
$script = require base_path('chisel.php');

$providedAnswers = $this->option('answers') === null
? []
: json_decode((string) $this->option('answers'), true, 512, JSON_THROW_ON_ERROR);

$answers = $script
->collectAnswers()
->onQuestion(fn (Question $question): array => multiselect(
label: $question->label,
options: $question->options,
default: $question->default ?? [],
required: $question->required,
hint: $question->hint,
))
->interactive($this->input->isInteractive())
->withAnswers($providedAnswers);

$skipNode = $this->shouldSkipNode();

if (! $skipNode) {
$this->installNodeDependencies();
}

$script->chisel($answers);

if (! $skipNode) {
$this->buildAssets();
}

return self::SUCCESS;
}

protected function shouldDeferInstallerHooks(): bool
{
if ($this->option('answers') !== null) {
return false;
}

return $this->installerFlag('LARAVEL_INSTALLER_DEFER_HOOKS');
}

protected function shouldSkipNode(): bool
{
return $this->installerFlag('LARAVEL_INSTALLER_NO_NODE');
}

protected function installerFlag(string $name): bool
{
return filter_var(
Env::get($name, Request::server($name) ?? getenv($name)),
FILTER_VALIDATE_BOOL,
);
}

protected function installNodeDependencies(): void
{
$npm = Chisel::in(base_path())->npm();
$packageManager = $npm->packageManager();

spin(
fn () => $npm->install(),
"Installing dependencies with {$packageManager->value}...",
);
}

protected function buildAssets(): void
{
$npm = Chisel::in(base_path())->npm();

spin(
fn () => $npm->run('build'),
'Building assets...',
);
}
}
6 changes: 6 additions & 0 deletions app/Http/Controllers/Settings/SecurityController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ class SecurityController extends Controller
public function edit(TwoFactorAuthenticationRequest $request): Response
{
$props = [
/* @chisel-2fa */
'canManageTwoFactor' => Features::canManageTwoFactorAuthentication(),
/* @end-chisel-2fa */
/* @chisel-passkeys */
'canManagePasskeys' => Features::canManagePasskeys(),
'passkeys' => Features::canManagePasskeys()
? $request->user()
Expand All @@ -37,15 +40,18 @@ public function edit(TwoFactorAuthenticationRequest $request): Response
->values()
->all()
: [],
/* @end-chisel-passkeys */
'passwordRules' => Password::defaults()->toPasswordRulesString(),
];

/* @chisel-2fa */
if (Features::canManageTwoFactorAuthentication()) {
$request->ensureStateIsValid();

$props['twoFactorEnabled'] = $request->user()->hasEnabledTwoFactorAuthentication();
$props['requiresConfirmation'] = Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm');
}
/* @end-chisel-2fa */

return Inertia::render('settings/Security', $props);
}
Expand Down
2 changes: 2 additions & 0 deletions app/Http/Middleware/HandleInertiaRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ public function share(Request $request): array
'user' => $user,
],
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
/* @chisel-teams */
'currentTeam' => fn () => $user?->currentTeam ? $user->toUserTeam($user->currentTeam) : null,
'teams' => fn () => $user?->toUserTeams(includeCurrent: true) ?? [],
/* @end-chisel-teams */
];
}
}
4 changes: 4 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
/* @chisel-teams */
use Illuminate\Database\Eloquent\Collection;
/* @end-chisel-teams */
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
Expand Down Expand Up @@ -56,7 +58,9 @@ protected function casts(): array
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
/* @chisel-2fa */
'two_factor_confirmed_at' => 'datetime',
/* @end-chisel-2fa */
];
}
}
Loading