petar-spasic / laravel-house
House skills for Laravel projects, installed into Claude Code by Laravel Boost: project setup with per-layer CLAUDE.md rules, Docker hosting behind Caddy, a git-backed kanban board with worktree stacks and Claude Code agents, and the validation:export command.
Requires
- php: ^8.3
- laravel/framework: ^12.0|^13.0
Requires (Dev)
- laravel/boost: ^2.10
- laravel/pint: ^1.32
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^4.0|^5.0
Suggests
None
Provides
None
Conflicts
Replaces
None
This package is auto-updated.
Last update: 2026-10-08 21:24:32 UTC
README
- Introduction
- Installation
- Starting a Project
- Modules
- What Every Project Gets
- Deployment
- Exporting Validation Rules
- The Kanban Board
- Kanban Configuration
- The Board
- The Board UI
- Working With Claude
- Worktree Stacks
- Team Sync
- Kanban Commands
- Kanban Troubleshooting
- Updating
- License
Introduction
Laravel House is a set of skills for Claude Code. A skill is a set of instructions that Claude loads when a task needs it. These skills start a Laravel project on one fixed set of rules, called "the house", and run it in Docker.
You tell Claude what you are building. Claude asks a few questions and installs the packages. Then it writes a
CLAUDE.md rule file for the app and one for each layer directory, such as app/Models. Claude follows those rules
in every later session.
There are five skills:
laravel-project-setupstarts a project, or brings an older house project up to date.laravel-deploymentruns the project in Docker, locally and in production.implement-kanbanputs the project on the kanban board.kanbanruns the board, once the project is on it.kanban-statustells you what is happening on the board right now.
The composer package also ships two tools:
- the kanban board: the
vendor/bin/kanbancommand and a web page at/kanban; - the
validation:exportArtisan command.
The package is a dev dependency. Nothing it adds runs in production.
The house ships rules, not frontend code. You get no login pages, no UI components and no SvelteKit files. Each rule file says what your project must build and how. Your project builds each piece when it needs it.
Note
The house is opinionated. Every project uses Postgres, Redis, Octane, Horizon, Fortify, Socialite and Sanctum, and end-to-end tests only. When your project needs something the house does not cover, Claude asks you instead of guessing.
Installation
You may install the skills in two ways. Both serve the same copy of each skill.
- The plugin gives you the skills in every directory on your machine. Use it to start new projects.
- The composer package pins the skills to one project, at the version in its
composer.lock. Only the package brings the board andvalidation:export.
You need:
- Claude Code;
- PHP 8.3 or newer, with Composer;
- Laravel 12 or 13, for the composer package;
- Node with npm;
- git 2.42 or newer;
- Docker with Compose v2;
- the
ghCLI, if Claude should create the GitHub repository.
Installing the Plugin
Install the plugin once per machine, at user scope:
claude plugin marketplace add petar-spasic/laravel-house claude plugin install laravel-house@laravel-house --scope user
Plugin skills carry the plugin's name as a prefix, for example /laravel-house:laravel-project-setup.
Installing the Composer Package
The setup skill adds the package to every project it starts. To add it to another project, require it as a dev dependency:
composer require --dev petar-spasic/laravel-house
Next, add "petar-spasic/laravel-house" to the packages list in boost.json, then run Boost's update:
php artisan boost:update
Laravel Boost is Laravel's toolkit for AI agents. Its update copies each skill
into the project's .claude/skills/ directory. These copies have no prefix, for example /implement-kanban. The
package's house rules join your CLAUDE.md only in a project set up on them, one with a config/house.php.
Laravel registers the package's commands and routes on its own. There is nothing to configure. The board waits until you adopt it.
Using Both
Warning
With the plugin and the package both installed, a project sees every skill twice.
The project's own copy is the pinned one. Turn the plugin off for that project:
claude plugin disable laravel-house@laravel-house --scope project
The setup skill does this for you.
Starting a Project
Open Claude Code in an empty directory, or in a fresh Laravel app, and run the setup skill:
/laravel-house:laravel-project-setup
If the directory has no Laravel app or no git repository yet, the skill creates them.
The Questions
Claude asks you these questions together:
- Frontend: Blade and htmx, htmx with Svelte islands, a SvelteKit app, API-only, or something else.
- Reverb: realtime broadcasting. The default is no.
- Multi-tenancy: the default is no.
- Slug: a short name in lowercase letters and digits, such as
acme. It names the test database (acme_test), the config file (config/acme.php) and the dev accounts' email domain. - Product name.
- Git remote: an
originURL, or a new private GitHub repository. - Production host name, if you know it.
- What you are building. You may leave this open.
Claude settles three things itself and shows them to you:
- the PHP minor version;
- free ports on your machine for the web server, Postgres and Redis, plus Reverb with that module (with the SvelteKit app, Reverb shares the web port);
- the LAN URL, for opening the app from another machine.
The house has no rules for another frontend, such as React, Vue or Inertia. If you pick one, Claude asks you for its rules before it writes any code.
What Gets Installed
Claude shows you one batch: the composer and npm packages, the files it will delete, and the decisions so far. You approve it once. Nothing outside the batch is installed.
The installer then writes the rule files and the few files the house ships:
- the prefixed-id trait;
- a middleware that makes Fortify answer with JSON until your auth pages exist;
- the middleware that gives each request an id for the logs, and the one that sets the security headers;
- the four seeders;
- the Horizon gate, which decides who may open the Horizon dashboard;
- the Boost config and the house's changes to Boost's guidelines;
- end-to-end tests for sign-in, two-factor authentication, Horizon access and seeding;
- the htmx boot files, with htmx;
- the islands boot file and island component, with islands.
After Setup
After the install, the deployment skill puts the project in Docker. Claude then commits, with your OK.
Claude ends with two lists: what was decided, and what is still open. Type /implement-kanban to record both on the
board. If the command is not found, run /reload-skills first.
Modules
A module is an optional part of the house. The frontend module decides how pages reach the browser. Reverb and tenancy add to any frontend. You choose modules once, at setup.
Blade and htmx
Laravel renders the pages with Blade. htmx swaps parts of a page without a full reload.
- Your project caches public pages, both as whole pages and as fragments. The rules say how.
- Links prefetch when the pointer hovers over them.
- Locally, a saved CSS or JS change shows in the browser at once, on the app's own URL.
You build the auth pages yourself, as Blade views: login, registration, password reset, the two-factor challenge,
password confirmation, email verification and passkeys. The rules in resources/CLAUDE.md say how. Until the pages
exist, Fortify serves no pages and answers with JSON only.
Svelte Islands
An island is a Svelte 5 component inside a Blade page. Use one where a page needs state in the browser.
- Components come from shadcn-svelte. You add each one when an island first needs it.
- One set of design tokens styles both the islands and the Blade pages.
SvelteKit App
Two apps live in one repository. Laravel is an API-only backend. A SvelteKit app
lives in frontend/ and runs on Node.
- Setup writes only
frontend/CLAUDE.md. You create the app yourself, following it. It names the allowed packages and what the first frontend change must deliver. - Pages render on the server by default. A page whose data is known at build time is prerendered, which means it is built once into a static HTML file.
- Both apps share one origin, so there is no CORS. The web server sends Laravel's paths (the API, Sanctum, Horizon, the health check) to Laravel, and every other path to SvelteKit. The exact list is in the deployment skill's spa reference.
- Browsers sign in with Sanctum's session cookie. Fortify answers under
/api/auth, and your API lives under/api/v1. - The rules cover shadcn-svelte components, forms built with Superforms and
validation:export, and Playwright browser tests.
Note
The production image cannot build until frontend/ exists. The local stack runs without it. Laravel's paths, such
as /up and /api/v1, answer. Every other path fails until the app exists.
API-Only
Without a frontend module, Laravel serves JSON under /api/v1. Clients sign in with a Sanctum API token.
Reverb
Reverb is Laravel's WebSocket server. It pushes broadcast events to the browser. The module adds a Reverb process and the rules for events. Choose it only when a screen needs live updates.
Multi-Tenancy
Note
Tenancy is opt-in, and it ships as rules your project implements, not as code. An app without it gets none of the rules or database roles below.
A tenant is one customer, such as a company, sharing the app with other customers. The house supports one kind of tenancy:
- One database. Every row a tenant owns has a
tenant_id. - No subdomain per tenant, no database per tenant, and no tenant in the URL.
- The current tenant comes from the session or from the API token. A user who belongs to several tenants switches between them. An API client holds one token per tenant.
The tenant is enforced twice. An Eloquent scope adds it to every query. Postgres row-level security (RLS) makes the database itself refuse other tenants' rows. A record of another tenant answers 404.
With tenancy, the app connects to Postgres as a role that does not own the tables and cannot bypass RLS. Migrations run as the owner. The deployment skill's tenancy reference sets up both roles.
Combining Modules
Pick one frontend:
| Frontend | Modules |
|---|---|
| API-only | none |
| Blade and htmx | htmx |
| htmx with Svelte islands | htmx, islands |
| SvelteKit app | spa |
Then add reverb, tenancy, both or neither. Islands need htmx, and the SvelteKit app excludes htmx and islands.
Setup refuses any other combination.
What Every Project Gets
Whatever modules you choose, you get the following.
Postgres and Redis
- Postgres holds the data. Redis holds the cache, the queues and the sessions.
- The same drivers run locally and in production. There is no SQLite.
- Tests run queues inline and keep the cache and sessions in memory.
- Tests use their own database,
<slug>_test.
Seed Data
Four seeders each have one job:
ReferenceDataSeederholds the data the app needs everywhere.DevSeederholds the local dev accounts and sample data.ProductionSeedercreates the production accounts: the operator account and the admins.DatabaseSeederpicks between them.
Every seeder is safe to run twice. The production image seeds reference data on every boot, and the accounts only
when DATABASE_SEED=true.
Octane and Horizon
- Octane keeps the app in memory between requests. Production runs it on FrankenPHP, a PHP server built on Caddy.
- Horizon runs every queue and shows them on a dashboard at
/horizon. - The dashboard admits the admins listed in
ADMIN_EMAILS, once their email is verified. Locally it also admits requests from your own machine.
Authentication
Every project gets three Laravel auth packages:
- Fortify handles sign-in, registration, password reset, two-factor authentication and passkeys. It is the backend; your frontend builds the pages.
- Socialite signs users in with Google, GitHub and other providers. It is installed but not wired. The rules say how to build it when you turn a provider on.
- Sanctum protects the API. With the SvelteKit app, the browser uses its session cookie. With every other module, the API accepts tokens only, and pages use normal web routes.
Password confirmation accepts a password or a passkey, nothing else. A user who signed up through a provider has no password, so they set one through "Forgot password".
A passkey sign-in skips the two-factor challenge. If your project needs the challenge there too, the rules show how to refuse passkey sign-in for users with two-factor on, or to send them to the challenge after the passkey.
Note
Locally, passkeys work only when you open the app at http://localhost:<web port>, never at a LAN address.
End-to-End Tests
- Every test drives a whole flow through its real entry point. Pest HTTP tests live in
tests/E2E. The SvelteKit app adds Playwright browser tests. - Only paid or outside services are faked.
- There are no unit tests. The house replaces Boost's unit-test guidance.
Prefixed IDs
Every model id looks like inv_7Kq2mZp9Xt4LwRc1: a short prefix for the model, an underscore, and 16 letters and
digits. This is the style Stripe uses.
- Migrations use
prefixedId()andforeignPrefixedId(). - The
userstable keeps Laravel's integer id.
Rules Per Layer
- The root
CLAUDE.mdholds what you are building, the hosting, and the rules for the whole project. - Each layer directory, such as
app/Modelsorroutes, has its ownCLAUDE.md. Claude reads it when it works there. - A change to the folder layout, the auth model or the routes updates your part of the matching rule file in the same commit.
The house rules stay current. Setup records your modules and versions in config/house.php, and every
composer update renders the rules again from it: Boost writes the root rules, and php artisan house:update the
house part of each layer file. Where your own rules go, and how to override a house rule, is in your root
CLAUDE.md, under "Where the docs live".
To remove the package from a house project, first take the house:update line out of post-update-cmd in
composer.json: Composer runs that list after the removal too.
Deployment
The deployment skill copies Docker templates into your project: Dockerfiles, compose files, web server configs and
entrypoint scripts. It also fills the Hosting section of your root CLAUDE.md.
There are two tiers: a local stack for development and a production image. A stack is the set of containers that runs the app. In each tier:
- One app container runs the web server, the scheduler and Horizon.
- Postgres and Redis run in their own containers beside it.
- Caddy is the web server.
The Local Stack
The local stack is your dev environment. You do not run php artisan serve or composer run dev.
- Caddy hands PHP requests to php-fpm, which runs a fresh PHP process for each request. So Xdebug breakpoints always fire, unlike under Octane.
- Caddy sends the dev-server paths to Vite, or to SvelteKit with the SvelteKit app. So hot reload works.
- Your source code is mounted into the container. Composer and npm install again on start when a lockfile changes.
- To open the app from another machine, set
LOCAL_APP_URLin.envto the URL that machine uses, such ashttp://192.0.2.10:<web port>. Then rundocker compose -f docker-compose.local.yml up -dagain. - The kanban board can run one stack per git worktree, from the same compose file. A worktree is an extra checkout of the repository in its own directory. See Worktree Stacks.
Warning
The local stack listens on your LAN by default, and the board at /kanban has no login. Set WEB_BIND=127.0.0.1
in .env to keep the stack on your machine, or set KANBAN_UI_TOKEN to protect the board. Either one also keeps a
card's agent, which works inside its own container, from editing cards through your main stack's board page. The
page cannot approve or finish a card. Your main stack's database and Redis listen on 127.0.0.1 only, out of the
containers' reach.
The Production Image
- FrankenPHP runs Octane as a non-root user. With the SvelteKit app, a Node process renders the pages behind the same Caddy.
- TLS ends at a reverse proxy on your server. The image publishes its port on
127.0.0.1only. - In
.env.prod, setOCTANE_WORKERSto a number that fits your server's memory. - In
.env.prod, setTRUSTED_PROXIESto your reverse proxy's address, as the container sees it. That is usually the Docker network's gateway. With the SvelteKit app, also add127.0.0.1. The example leaves it empty, and the container refuses to start until it is set.
Running the Stack
You may start the local stack and check its health with:
docker compose -f docker-compose.local.yml up -d --build --wait
docker compose -f docker-compose.local.yml exec app healthcheck.sh
Open the app at http://localhost:<web port>. Claude picks the web port at setup, and the Hosting section of your
CLAUDE.md names it.
To run the tests inside the stack:
docker compose -f docker-compose.local.yml exec app php artisan test
One test run uses the test database at a time. A second run waits until the first one ends.
With the SvelteKit app, browser tests run only through this script:
docker compose -f docker-compose.local.yml exec app docker/e2e.sh
The production image reads .env.prod. Copy the example, fill it in, then start the image:
cp .env.prod.example .env.prod docker compose --env-file .env.prod up -d --build --wait
When a container starts but serves nothing, check the Traps in the deployment skill. Each one names a symptom, its cause and the fix.
Exporting Validation Rules
This command is for the SvelteKit app. Your FormRequests stay the one place where validation rules live, and the server stays the authority. The command turns the rules into Zod schemas, so a form shows the same errors in the browser before it is sent.
Mark a FormRequest with the attribute and a name:
use PetarSpasic\LaravelHouse\Validation\ExportValidation; #[ExportValidation('register')] final class RegisterRequest extends FormRequest { // ... }
Then run the export:
php artisan validation:export
- Each marked request becomes one TypeScript file in
frontend/src/lib/validation/generated/, such asregister.ts. It holds a Zod schema, plus the messages and field names of each locale. - Rules that need the database or the user, such as
unique,exists, closures and custom rule objects, stay on the server. The file lists them in a comment. Their errors reach the form through Laravel's 422 response. - An array whose keys are names you choose is a map, such as
settingswith one entry per setting. Name it on the attribute,#[ExportValidation('preferences', maps: ['settings'])], or the file treats it as a list. - When a rule cannot be translated, the command fails and names the class, the field and the rule.
- Commit the generated files.
php artisan validation:export --checkfails when they no longer match the requests, and when an exported form has no parity spec atfrontend/e2e/parity/<name>.spec.ts. The frontend rules makenpm run checkrun it first.
Note
The command ships in this dev package. In production the attribute does nothing, so an install without dev dependencies validates as usual.
The Kanban Board
The kanban board lives inside your git repository, and Claude agents work through its cards. The board is a set of
JSON files on its own branch. You manage it from the web page at /kanban, from vendor/bin/kanban, or by asking
Claude. Every change is a git commit, so the board has a full history, and your team shares it like any other branch.
Each card Claude works on gets its own clone of the repository, its own branch and its own Docker stack. So several cards are worked on at once without stepping on each other.
Every card follows the same path:
- You describe the work. You, or Claude, add a card with a title, a description, an area and a list of
acceptance criteria. When the card is complete enough to work from, it moves to the
planningcolumn. - A planner plans it. A
kanban-planneragent reads the code in the card's own clone and Docker stack, and writes the plan the worker follows: the files, the steps with a check for each, and how each criterion is proven. The card moves toready. - Claude starts the card. The main Claude Code session claims the card. It creates a clone for it in
.claude/worktreesand brings up a Docker stack for it on its own ports. - A worker writes the code. A background
kanban-workeragent follows the plan and commits to the card's branch. When it is done, it reports back, and the card moves toreview. - An evaluator checks it. A read-only
kanban-evaluatoragent checks every acceptance criterion and approves or rejects the work. Rejected work goes back to the worker. - Approved work is merged.
kanban finishmerges the branch intomain, marks the carddone, and removes the clone and its stack. Every few merges, it pushesmaintoo. - The run is published. At the end of a run,
kanban publishpushes the board andmainto your remote.
Requiring the package does not put a project on the board. The /implement-kanban skill does:
- It runs the installer, below.
- It archives the decisions your project has made, and puts each open question on the card that waits for it.
- It prepares one Docker stack per card.
- It runs a first card through the agent loop.
Note
Only you can start this skill, by typing /implement-kanban. Claude never starts it on its own.
Adopting the Board
The skill runs the kanban:install Artisan command from your project's main checkout. The --key option sets the
prefix of your card IDs. For example, --key=ACME gives cards like ACME-7K2QF9:
php artisan kanban:install --key=ACME
First, --check lists what the installer needs from your project and machine, such as git 2.42 or later, a reachable
origin that is not empty, and Docker Compose. It changes nothing. A fail line also stops the install itself. To
see what the installer will change before it changes anything, add --dry-run. Then check the wiring. The
doctor command prints ok, warn or fail for each check:
vendor/bin/kanban doctor
The installer makes the following changes:
- It creates the board on a branch named
kanbanand checks it out atdocs/kanban. This branch shares no history with your code. - It configures git on this machine: a merge driver for the board's files, and the package's git hooks. The hooks
reject
Co-Authored-Bytrailers, and pushes from a card's worktree. - It adds hooks and a permission for
vendor/bin/kanbanto.claude/settings.json. Hooks that are already there are kept. It also turns off Claude Code's commit and PR attribution, because the hooks would reject those trailers. - It adds the permissions that name this checkout's path, for its
vendor/bin/kanbanandvendor/bin/kanban-exec, to.claude/settings.local.json. That file stays out of git, so each machine gets its own. - It writes the three agents to
.claude/agents/kanban-planner.md,.claude/agents/kanban-worker.mdand.claude/agents/kanban-evaluator.md. - It writes a marked
## Kanbanblock into the rootCLAUDE.md, before Boost's guidelines when they are there. The block points Claude at thekanbanskill. - With Boost, it makes sure
petar-spasic/laravel-houseis in thepackageslist inboost.json, sophp artisan boost:updatecopies thekanbanskill into.claude/skills/. Without Boost, the block points at the skill insidevendor/petar-spasic/laravel-house. - With an ssh
originand a local compose file, it creates a deploy key for this clone at.git/laravel-house/deploy_key. See Syncing From a Container. - It adds
/docs/kanban/,/.claude/worktreesand/.claude/settings.local.jsonto.gitignore. - It adds the card-stack lines to a local compose file written before them. See Where Agents Run.
Review these changes and commit them to main. The settings in .claude/settings.json apply to everyone who clones
the project.
Note
Claude Code loads agents and hooks only when a session starts. Restart Claude Code after installing.
Warning
The package's git hooks work by pointing core.hooksPath at them, so hooks in .git/hooks stop running. If
core.hooksPath is already set, for example by Husky, it is kept and the package's hooks do not run.
vendor/bin/kanban attach --force replaces it. To keep Co-Authored-By trailers and Claude Code's attribution, set
githooks.reject_co_authored to false in config/kanban.php before you install. On a project already installed,
set it, run vendor/bin/kanban doctor --fix, and remove the attribution object from .claude/settings.json
yourself.
Joining an Existing Board
Each other machine, and each fresh clone, needs the board checked out, git configured, and the permissions that name
its own path in .claude/settings.local.json. Run the following after cloning:
composer install vendor/bin/kanban attach
A new Claude Code session also runs attach by itself when the board is missing.
Kanban Configuration
Most projects need no configuration. To change the defaults, publish the configuration file to config/kanban.php:
php artisan vendor:publish --tag=kanban-config
You may also set the most common settings in your .env file:
KANBAN_SYNC=auto # auto, on or off. See "Team Sync". KANBAN_USER=Ana # Your name in the board's history. Defaults to git's user.name. KANBAN_MAIN_BRANCH=main # The branch cards are merged into. KANBAN_MAX_STACKS=12 # How many card stacks may run on this machine at once. KANBAN_PULL_SECONDS=30 # How often an idle board asks for other people's changes. KANBAN_UI=true # Set to false to turn off the /kanban page. KANBAN_UI_TOKEN= # Set to make the /kanban page ask for this token once per browser. KANBAN_GIT_TOKEN= # An https remote only: the token the local container syncs the board with. KANBAN_AGENT_SHELL=container # Set to host to run agents' commands on this machine instead of in their stack. KANBAN_UPSTREAM=false # Set to true to let Claude file package issues. See "Reporting Package Issues".
Agent Models
By default, the planner runs on Opus with high effort, the worker on Sonnet with high effort, and the evaluator on Opus
with medium effort. You may change any of them in the agents section of config/kanban.php:
'agents' => [ 'planner' => ['model' => 'opus', 'effort' => 'high'], 'worker' => ['model' => 'sonnet', 'effort' => 'high'], 'evaluator' => ['model' => 'opus', 'effort' => 'medium'], ],
kanban run passes these values to every agent it starts as --model and --effort, and names them on the line that
says it started the agent. It reads them again for each agent, so a change applies from the next agent it starts, with
no restart. The model and effort of the session that runs it never reach its agents.
The values are also written into the agent files, which decide for agents your session spawns itself. After changing
them, run vendor/bin/kanban doctor --fix and restart Claude Code.
Agents that kanban run starts have nobody to approve a tool. They may edit their card's files and run commands in
its container. Besides that, they may use only the tools in allowed_tools, which by default are WebFetch and
WebSearch. An empty list keeps them off the web:
'agents' => [ 'allowed_tools' => [], ],
Quality Gates
A worker cannot hand in its work until every command in gates.report passes on its branch. The gates run in the
card's stack. By default, they are Pint, a check of new migration timestamps, a check that every id in database/data
stays unless the branch adds a migration, and npm run check in frontend/ when the project has one. A gate with when runs only where that path exists, and timeout gives it more than the default
120 seconds:
'gates' => [ 'timeout' => 120, 'report' => [ 'vendor/bin/pint --test --diff={main_branch}', 'vendor/bin/kanban migrations --base={main_branch}', ['run' => 'vendor/bin/kanban data-ids --base={main_branch}', 'when' => 'database/data'], ['run' => 'cd frontend && npm run check', 'when' => 'frontend/package.json', 'timeout' => 300], ], ],
Agents run them with vendor/bin/kanban gates. Publishing config/kanban.php replaces the whole gates list, so copy
the defaults you keep.
Commands After Merging
After finish merges a card into main, it takes the card's stack and clone down. Then it installs your dependencies
when the card changed composer.lock or a package-lock.json. In projects with a worktree stack,
it then runs the migrate command and the commands in finish.after, read from the merged code. They run in your main
stack's app container, as the agents run them in theirs, and finish starts the stack first when it is down. By
default, they seed reference data:
'migrate' => 'php artisan migrate --force', 'finish' => [ 'after' => [ 'php artisan db:seed --class=ReferenceDataSeeder --force', ], 'check' => [], ],
A db:seed --class=… command is skipped while that seeder does not exist. List your test suite in finish.check to
run it on main after each merge: while it fails, the next finish waits. finish also pushes main once
publish.every merges (5 by default) are not on your remote. When the merge changed a lockfile, a docker file or the
compose file, finish rebuilds your main stack before these steps. It builds the images first, while your stack
keeps serving, then recreates the containers. If that fails, it prints the command to run, and the card stays done.
Pass --no-rebuild to skip it.
When a step after the merge fails, the card is still done, and the steps after it still run. Only a failed install
skips some: migrate, finish.after and finish.check. finish exits with code 10, and kanban run reports each
failure to Claude without blocking the card.
A card that changes the files that steer the agents or git (.claude/, config/kanban.php, a hooks directory,
.gitattributes) merges only with your approval. When you plan such a change, approve it up front:
vendor/bin/kanban allow-steering ACME-7K2QF9 config/kanban.php
An approval given before the card changes the file covers any change to it. Otherwise kanban run asks you on the
card once the evaluator approves it, with the diff to read, and your answer to questions merges the card or sends it
back to its worker. That answer approves the file as it is: a later change to it asks again.
An approved card keeps its approval when main moved only in files that match finish.overlap_ignore (Markdown files
and docs/ by default), or in none of the files the card changes; otherwise finish asks for a refresh and a new
review. A list of your own replaces the defaults, so repeat *.md and docs/* in it. When the refresh merges
cleanly, the new review runs the gates and the whole suite once. With a suite listed in finish.check, which runs on
main after the merge, it runs only the gates and the tests of the files both main and the card changed.
The Board
Boards and Cards
The installer creates one board, work. On disk, each card is a JSON file under docs/kanban:
docs/kanban/
├── kanban.json # Board-wide settings: key, WIP limits, locked stages
├── decisions.md # Decisions recorded before questions moved onto cards (read-only)
├── _epics/
│ └── passkey-login.json # An epic: title, goal, done-when
└── work/ # The board
├── board.json
└── ACME-7K2QF9.json # A card
A card has a type (feature, bug, chore or spike), a priority (urgent, high, normal or low), labels,
a description in Markdown, acceptance criteria and, optionally, other cards it depends on. Two things group cards:
- The area is a label such as
area:billing: a permanent part of the product. Every card has one, and two cards on the same area never run at the same time. The board gives each area a colour of its own (the first ten never share one), and an area keeps its colour as new ones come. - The epic is a finite goal, such as
passkey-login, with a title, a goal and the conditions that finish it. A card belongs to at most one epic.kanban epiclists the epics and how many of their cards are done.
Click an area or an epic on a card to show only its cards; click it again to show all of them. The filter stays in the page address, so you can share a filtered board.
Warning
Never edit the files in docs/kanban by hand. Use the UI, the kanban command or Claude. Each of them validates the
change and commits it.
Stages
Cards move through these stages:
| Stage | Meaning |
|---|---|
backlog |
An idea, not yet ready to be worked on. |
planning |
Fully described; a planner agent writes its plan. |
ready |
Planned, and waiting for a worker. |
doing |
A worker is on it, in its own worktree. |
review |
The worker is done; the evaluator checks the work. |
done |
Merged into main. |
dropped |
Not going to happen. Dropping a card asks for a reason. |
You move cards from backlog to planning, back to backlog, and to dropped. A card enters ready through its
plan. The rest of the path belongs to the workflow: a card enters doing through start, review through the
worker's report, and done through finish.
Card IDs
A card ID is your key followed by six random characters, such as ACME-7K2QF9. Wherever a command asks for an ID,
you may type just its start: at least three characters after the key, as long as they match only one card. Case does
not matter, and you may leave the key out:
vendor/bin/kanban show 7k2
Planned Cards
A card may leave the backlog only when it is complete enough for an agent to work from:
- it has an
area:label, a title and a description; - it has between 1 and 24 acceptance criteria;
- every card it depends on exists;
- it is not blocked, and has no open question.
Write each acceptance criterion as something the evaluator can check, such as "GET /invoices.csv lists the month's
invoices". kanban promote tells you which rule a card misses.
The card then waits in planning. Once the cards it depends on are done, a planner agent takes it, in a clone and a
stack of its own. The planner reads the code, checks every name against it, and writes the plan for the worker: the
files to read and change, the steps in order with a check for each, how each criterion is proven, and the traps. Then
the card moves to ready.
The worker is a smaller model, so the plan is short and exact. It says what to build and where, and leaves the code to
the worker. kanban plan refuses a plan that names a file the code does not have or leaves a criterion out. When a plan
runs long, writes the code itself, or names a docker command the worker cannot run inside its container, it prints a
hint, and the planner revises it.
A plan holds while the card stays as it was planned. A ready card goes back to planning when you change its criteria
or description, or answer one of its questions other than by confirming the option its plan took. A card that comes
back from a question is planned again too: its planner reads your answer and any work so far, and plans the rest.
If you already have a plan for a card, give it one yourself. The file needs the same sections:
vendor/bin/kanban plan ACME-7K2QF9 --plan-file=plan.md
Locked Stages
Once work starts, the worker and the evaluator rely on the card as they read it. So cards in doing, review and
done are locked. You may still add a note, block or unblock the card, and tick or untick criteria. You may also
reword a criterion with a reason, which the agents see:
vendor/bin/kanban set ACME-7K2QF9 accept[2]="The export lists the month's invoices" --reason="The owner narrowed it"
Nothing else about it can change, and it cannot move to another board.
To edit a card in doing or review, put it back first:
vendor/bin/kanban stop ACME-7K2QF9 --to=ready
A card with work on its branch goes on to planning, where its planner plans the rest.
The list of locked stages is the locked setting in docs/kanban/kanban.json.
The Board UI
Opening the Board
The board's web page is at /kanban in your app, for example http://localhost:<web port>/kanban. It is available
only in the local environment and only from the main checkout. To keep it off your LAN, see
The Local Stack.
The page shows one column per stage, with the cards in the order Claude will pick them up. It refreshes every few seconds, so changes made by Claude, the command line or a teammate appear on their own.
- The page does not appear while your routes are cached. Run
php artisan route:clearif it is missing. - It needs nothing from your app: no session, login, Vite or Tailwind.
- It needs a browser from 2024 or later.
- It is set in the Inter font, which ships with the package under the SIL Open Font License.
Editing Cards
Click a card to open it. Fields save on their own: the title and the criteria when you press Enter or leave them,
checkboxes and dropdowns at once. Text such as the description opens an editor that saves with Save or
Ctrl+Enter. Saved appears in the header. Cards it links to, such as its dependencies, open on top of it. Press
Esc to close the top one.
To move a card, drag it to another column, or press m. Press n to add a new card.
A planned card shows its plan under the acceptance criteria, folded. A card waiting for your answer shows a Question tag, and its panel shows the question. Write the answer in the description, then press Unblock. A Blocks N tag marks a card that N open cards wait on: if they are one piece of work, fold them into it.
If someone else edits the same text while you are typing, nothing is overwritten. Your text stays in the editor, the other version appears beside it, and you choose Keep mine or Use theirs.
Keyboard Shortcuts
Press ? on the page to see every shortcut. The most useful ones are:
| Key | Action |
|---|---|
/ |
Search |
j k |
Next or previous card |
h l |
Column to the left or right |
Enter |
Open the card |
n |
New card |
m |
Move the card |
p |
Change the priority |
b |
Switch board |
Shift+1…9 |
Save this board to a number key |
Alt+1…9 |
Go to a saved board |
Esc |
Close the card on top |
t |
Switch between light and dark |
Working With Claude
Running the Board
Open Claude Code in your project's main checkout and ask it to run the board:
Run the board.
The main session follows the kanban skill. The routine runs in code, in vendor/bin/kanban run, which the session
keeps going in the background:
- it moves complete cards from
backlogtoplanning, and has a planner plan each one; - it starts as many planned cards as the limits allow, with a worker for each; planners and workers share the limit;
- it sends finished work to the evaluator and merges what is approved;
- a card that waits on your answer goes back to
backlog, and the board carries on with the others. Once you answer, its kept branch starts again ahead of new cards.
Each agent is a headless Claude Code session of its own. This needs Linux and card containers (see
Where Agents Run). The main session steps in only when something needs judgment:
a blocked card, a failing merge, a red main, a package finding, or nothing left to start. When it hits your usage
limit, the board pauses and resumes later. When you tell Claude to wrap up, vendor/bin/kanban drain makes the run
finish the cards in flight and start no new ones, without stopping it.
To see what is happening at any time, ask Claude:
/kanban-status
To stop, tell Claude to wrap up. It starts nothing new, lets the cards in progress finish, publishes and sends you one summary.
Note
Only one Claude Code session per machine may run the board at a time. A session that ends lets go of the board, and
after /compact or a restart the new session takes over from the old one of the same conversation. If a session that
crashed, or started before you updated the package, still holds it, run vendor/bin/kanban lease --takeover, or wait
15 minutes.
Your Morning
Once a day, ask Claude for the morning:
Do the morning.
Claude shows what merged, what is blocked and what the agents spent, then asks every open question as a multiple
choice, with a recommended answer and what each option means. Your answers go onto the cards, and the waiting cards
move on. The cards the agents discovered wait for criteria, which you and Claude write. A failure the agents find
already on main becomes one high-priority card, however many agents meet it. The morning lists it until you give it
an area, so promote can take it. Tell Claude about new work, and
the board carries on.
Cutting Cards
Cards are written for agents. Each card costs an agent to plan it, an agent to build it, a third to review it, clones, stacks and a merge, so a card is one cohesive piece of work, not one small step. Cards that share an area never run at the same time, so related work on one area belongs on one card, and separate areas run side by side.
When two cards turn out to be one piece of work, fold them:
vendor/bin/kanban fold ACME-B7Q2PX --into=ACME-A1K8ZT
new and set print a hint: when a card's area already has an open card, or when many cards wait on one.
Questions and Rules
Decisions about what the product does are yours. A choice that is easy to change later does not wait for you: the
agent takes the recommended option, records it as a provisional decision, and you confirm or change it in the morning.
Anything else, such as money, legal text, real data or production, waits in backlog until you answer. Your answer
goes onto the card under an ## Owner answer heading. Claude's own notes on a card are information, never your
decision.
A rule that every card must follow, such as "money is stored in cents", goes into the CLAUDE.md file of the
directory it governs. Every agent reads those files.
Reporting Package Issues
When a worker or an evaluator meets a problem in this package itself, it records it on the card. List these findings
with vendor/bin/kanban upstream. With KANBAN_UPSTREAM=true and the GitHub CLI signed in to github.com, Claude files
each one as an issue on this package's repository, after searching for an open one:
vendor/bin/kanban upstream file ACME-7K2QF9:3H8D2K1Q
Claude files a problem it meets itself the same way:
vendor/bin/kanban upstream new "Finish ignores the lease — seen after a restart"
Both commands refuse any text that names your project: its key, app name, hosts, repository, paths, addresses or people. Without the setting, Claude lists the findings in its summary instead.
When Something Goes Wrong
Start with status. It shows the cards in progress with their agents, the blocked cards and anything that needs
attention. show gives one card in full:
vendor/bin/kanban status vendor/bin/kanban show ACME-7K2QF9
If a check fails, run vendor/bin/kanban doctor. It names the problem, and doctor --fix repairs the wiring.
To give up on a card, put it back. This ends the agent that kanban run started for it. If the card's branch has
commits, the branch is kept and reused the next time the card starts. A draining board does not start it again:
vendor/bin/kanban stop ACME-7K2QF9 --to=backlog --reason="Waiting on the payment provider"
Worktree Stacks
Each card's clone runs its own copy of your local Docker stack. It has its own ports, containers and database, so a worker can migrate, seed and test without touching your main stack.
The stack is built from your project's docker-compose.local.yml. The start command writes a .env into the
clone with the stack's name and ports, then runs docker compose up. finish and stop take the stack down
again.
Where Agents Run
A card's agents work in its clone and in its stack's container. Their shell commands run inside the container, git
included, so tests, Artisan, npm and browser checks run where your app does. A plain vendor/bin/kanban command runs
on your machine. Their file tools reach only the card's clone.
The local compose file mounts the card's clone at its own path too, gives the card a TMPDIR inside it, and keeps the
deploy key out of card stacks:
services: app: volumes: - ./:/app - ./:${KANBAN_WORKTREE_PATH:-/app} environment: TMPDIR: ${KANBAN_TMPDIR:-/tmp}
vendor/bin/kanban doctor --fix adds these lines to a compose file written before them. Set KANBAN_AGENT_SHELL=host
to run agents' commands on your machine instead.
Preparing Your Compose File
Many copies of the stack run at once, so nothing in your compose file may use a fixed name or a fixed host port. The deployment skill's local compose file already follows these rules:
- the top-level
name:is"${COMPOSE_PROJECT_NAME:?…}", so a stack never starts without a name; - no
container_name, and no volume or network name that is not built from${COMPOSE_PROJECT_NAME}; - every published host port comes from a variable, such as
WEB_PORT,DB_HOST_PORTorREDIS_HOST_PORT; - a service with
build:does not also setimage:.
Your main .env also needs a project name of its own, such as COMPOSE_PROJECT_NAME=acme-local.
The app container runs as HOST_UID and HOST_GID (1000 by default), because it writes your checkout and its .git.
They must be the ids of the user who owns the checkout, 0 when Claude Code runs as root. vendor/bin/kanban doctor
checks them, doctor --fix writes them into .env, and start refuses a card while they differ. Rebuild your main
stack after a change.
Each card's stack gets a block of ten ports. stack.ports in config/kanban.php places the three above in it. Any
other host port variable in your compose file, such as ${MAILPIT_PORT:-8025} for a mail catcher, takes the next free
port of the block, so you list nothing.
vendor/bin/kanban doctor checks these rules and names any line that breaks one. It also warns when phpunit.xml
sets DB_HOST or DB_PORT, because tests in a worktree would then hit your main database.
If you do not use Docker, set stack.compose_file to null. Cards then get a worktree without a stack.
Ports
Card stacks take their ports from the range 21000–21999, in blocks of ten. The first card gets WEB_PORT=21010,
DB_HOST_PORT=21011 and REDIS_HOST_PORT=21012, the next one gets 21020 to 21022, and so on. The block is reserved
across every project on the machine, so two projects never collide. Keep your main stack's ports outside that range.
vendor/bin/kanban stack ACME-7K2QF9 url prints a card's address.
Before it starts a stack, the package checks that the machine has room: at most KANBAN_MAX_STACKS stacks (12 by
default, room for six cards in doing and six in review, which keep their stacks), at least 8 GiB of free memory, at
least 20 GiB of free disk, and a load below 75% of the CPUs.
Docker Address Pools
Each stack is its own Docker network. If your local network overlaps Docker's default address ranges, Docker runs out
of networks after about six stacks, and compose fails. You may widen the ranges once, in /etc/docker/daemon.json:
{
"bip": "172.17.0.1/16",
"default-address-pools": [
{ "base": "10.210.0.0/16", "size": 24 },
{ "base": "172.16.0.0/12", "size": 16 }
]
}
Then restart Docker, check the headroom, and bring your stacks back up:
sudo systemctl restart docker vendor/bin/kanban doctor
doctor shows how many networks are free, and warns below KANBAN_MAX_STACKS.
Team Sync
Sharing a Board
If your project has an origin remote, the board is shared through it. The installer pushes the kanban branch
once. After that, every change is pushed as soon as it is made. Changes from others are pulled at most every 30
seconds: while the board page is open, when a Claude Code session starts, when status or next runs, and while
kanban run drives the board.
Teammates join with vendor/bin/kanban attach, as in Joining an Existing Board. When
two machines try to start the same card, only one of them gets it.
The board runs ahead of the code. finish marks a card done for everyone at once, but its code reaches your
teammates only when kanban publish pushes main.
Keeping a Board Local
To keep the board on your machine, add the following to .env before installing:
KANBAN_SYNC=off
Board changes then stay local until you run vendor/bin/kanban publish.
Note
A published config/kanban.php that reads env('KANBAN_SYNC', 'off') keeps sync off. Change the default to
'auto' to turn sync on.
Syncing From a Container
When your app runs in Docker, the board page syncs from inside the container. The container needs:
phpandgiton itsPATH;exec()allowed in PHP;- a user that can write the mounted
.gitdirectory; - permission to push to your remote.
For an ssh remote, kanban:install, attach and doctor --fix create a deploy key for this clone at
.git/laravel-house/deploy_key and print its public half. This happens when the project has a local compose file.
Ask a repository admin to add the key with write access:
gh repo deploy-key add .git/laravel-house/deploy_key.pub --allow-write
The container's git must use that key. The key is already inside the container through the project's mount. The
deployment skill's local compose file sets this for you. In another compose file with the project mounted at /app,
add:
services: app: environment: GIT_SSH_COMMAND: "${KANBAN_GIT_SSH_COMMAND-ssh -i /app/.git/laravel-house/deploy_key -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/app/.git/laravel-house/known_hosts}"
Your own git on the host keeps using your own key. To see what goes wrong in the container, run
vendor/bin/kanban sync inside it.
For an https remote, or a host that disables deploy keys, set KANBAN_GIT_TOKEN in .env to a fine-grained token
with read and write access to this repository's contents only. The deployment skill's local compose file hands it to
git in the main stack's container, and only for your remote's host.
When Two People Edit the Same Card
If two people change the same field of a card, the newer change wins. When the field is text, such as the title or the
description, the card's history keeps the older version. Open the card or run vendor/bin/kanban show to see it.
The history shows who made each change, such as Ana for what Ana did herself and worker (Ana) for her agents. The
name comes from KANBAN_USER, or from git's user.name when it is not set.
If a sync keeps failing, the board page shows a notice, and status and doctor print the reason.
Kanban Commands
Every command runs as vendor/bin/kanban <command> without booting your app, or as php artisan kanban:<command>.
The one exception is kanban:install, which runs through Artisan only. Add --help to any command for its options.
The protocol reference lists every flag and exit code.
Reading the board
| Command | Description |
|---|---|
status |
What is in progress, blocked and next, and any failing checks. |
list |
Cards in planning, ready, doing and review, plus blocked cards. |
show ID [--plan] |
One card with its criteria, dependencies and history; --plan prints only its plan. |
next |
The card that would be started next, or with --planning planned next. -v says why the others wait. |
upstream |
Package findings waiting to be filed. |
morning |
What merged, what is blocked, the questions, the discovered cards waiting for criteria, the failures on main waiting for an area and what the agents spent, since yesterday. |
questions |
Every question waiting for you, with its options: open questions on unfinished cards, and provisional decisions until you answer them. |
doctor |
Checks the installation. --fix repairs it. |
validate |
Checks every board file. --fix rewrites them. |
Changing the board
| Command | Description |
|---|---|
new work "Title" |
Adds a card. --epic=SLUG puts it in an epic. |
epic SLUG "Title" |
Adds or updates an epic: --goal= and --done-when=. Without a slug, lists the epics. |
fold ID --into=ID |
Merges cards into one. |
answer ID#N OPTION |
Records your answer to a question, then moves the card on. |
set ID key=value |
Changes a card, such as priority=high, epic=passkey-login or note="…". |
move ID STAGE |
Moves a card to another stage, or --board=BOARD to another board. |
promote ID |
Moves a card from backlog to planning, or to ready when its plan still holds. --auto fills planning with complete cards, up to 12 by default, and sends ready cards without a plan back to planning. |
plan ID |
Gives a card in planning a plan of your own (--plan-file=). A planner hands in its plan the same way. |
board BOARD "Title" |
Adds or updates a board. |
fold-boards |
Moves an older board onto one work board, with each card's epic taken from where it was. /implement-kanban runs it. |
Working on cards (usually run by Claude and kanban run)
| Command | Description |
|---|---|
run |
The routine of running the board: starts cards, agents and merges. Claude runs it for you. |
drain |
Makes run wrap up: it starts no new card and stops once none is in flight. drain --off undoes it. |
start ID |
Claims a card and creates its clone and stack: a ready card for its worker, a planning card for its planner. A branch that stop kept is reused, with the latest main merged in. A refusal names the card ahead on the same area, or the limit it hit. |
refresh ID |
Merges the latest main into the card's branch, once everything in its clone is committed. When the merge changes a lockfile, a docker file or the compose file, it recreates the card's stack. |
rebuild-branch ID |
Turns a card's branch into one commit with the same files, when a merge of main carries changes of its own. |
wait [ID] |
Waits until the card's agent has stopped and its plan, report or verdict is on the board. Without an ID, it waits for any card an agent works on. |
finish ID |
Merges an approved card into main and cleans up. Exit code 10 means the card merged, but a step after the merge failed. |
allow-steering ID PATH |
Approves a card's change to a file that steers the agents or git, so finish merges it. |
stop ID --to=STAGE |
Takes a card out of work, ends the agent kanban run started for it and cleans up. A branch with commits is kept for the next start. A card its planner has planned moves to ready this way. |
stack ID up|down|reload|logs|url |
Manages a card's stack. stack ID exec -- CMD runs a command in it. |
gates |
Runs the quality gates in a card. |
sync |
Pulls and pushes the board. |
publish |
Pushes the board and main. |
attach |
Checks out the board on this machine. |
Kanban Troubleshooting
The gotchas list problems met in real projects, each with its cause and its fix. The most common ones are:
- "Agent type not found", or the hooks do not run. Restart Claude Code after installing or updating.
setexits with "a locked stage". The card is in a locked stage:doing,reviewordone. See Locked Stages.- Several cards are ready but only one starts. They share an area.
vendor/bin/kanban next -vsays so, and a refusedstartnames the card ahead. - A ready card went back to
planning. Its criteria or description changed after it was planned, and its planner plans it again. - Every command says "an older board format". The board predates version 3: boards inside epic directories.
Run
/implement-kanban. - Compose fails after about six stacks. Widen the Docker address pools.
- Tests in a worktree hit your main database. Remove
DB_HOSTandDB_PORTfromphpunit.xml. - The board page answers 403 for a host name. Add that name to
ui.hostsinconfig/kanban.php.
Updating
How you update depends on how you installed.
Updating the Plugin
Update the plugin with:
claude plugin update laravel-house@laravel-house
Then restart Claude Code. The plugin has no version number, so it follows the default branch: every pushed commit is
an update. You may turn on auto-update under /plugin, in Marketplaces.
Updating the Composer Package
To move to the latest release, require the package with no version. If the project is on the board, let
doctor --fix rewrite the agents, the hooks, the CLAUDE.md block and the git config. Then refresh the skills:
composer require --dev petar-spasic/laravel-house vendor/bin/kanban doctor --fix php artisan boost:update
Skip the doctor line in a project without the board. Then restart Claude Code. Releases are semver tags.
Warning
While the package is at 0.x, ^0.N stays on 0.N.x, so composer update alone never reaches a new minor
version.
Warning
A project that still requires petar-spasic/laravel-kanban cannot require this package next to it: the two
conflict. Stop any running agents, then swap them in one shell call. Leave out vendor/bin/kanban doctor --fix when
the project has no board:
composer remove --dev petar-spasic/laravel-kanban && composer require --dev petar-spasic/laravel-house -W && vendor/bin/kanban doctor --fix && php artisan boost:update
Then restart Claude Code and run /implement-kanban. Its case for that package finishes the move on every clone.
Warning
Every clone that shares a board must run the same version of the package. Commit composer.lock. Each other clone
then runs composer install and vendor/bin/kanban doctor --fix.
Warning
Version 0.10 renders the house rules on every composer update. A house project set up earlier has no
config/house.php and keeps its rules as they are until it adopts the current core
once.
Warning
Version 0.9 adds the planning stage and its planner agent. Update every clone, run vendor/bin/kanban doctor --fix
to write the planner, and restart Claude Code. The next kanban promote --auto, which kanban run runs on every pass,
moves the ready cards that have no plan to planning.
Warning
Version 0.7 moves a board onto one work board: each card takes its epic from the directory it was in, and decision
cards become an archive. Finish or stop every card in progress, update every clone, then run /implement-kanban.
Until then, board commands refuse with an older board format.
Adopting the Current Core
The core is what every project gets. When the core changes, a project set up earlier may adopt it without running setup again.
- Update the composer package, then restart Claude Code. The project uses its own copy of the skills, not the plugin, so this step comes first.
- Ask Claude to adopt the current house core.
The setup skill then installs Sanctum and Socialite if they are missing. It moves your rule files onto the rendered house rules, keeping your own rules beside them, and merges the current routes and end-to-end tests into yours. Then the deployment skill puts Caddy in front of the local stack and removes the earlier local web server.
Warning
Adopting rebuilds the local image. Commit your work before you start.
Two changes are never part of adopting. Each one is a separate decision:
- adding tenancy to an app that already has data;
- moving an existing SvelteKit frontend to server rendering and
/api/auth. Adopting leavesfrontend/as it is.
License
Laravel House is open-sourced software licensed under the MIT license.