Spec-driven workflow for Laravel — integrated with
Laravel Boost.
You describe the product; a squad of thirty personas turns it into a PRD, a backlog, plans, code, and
tests — one story at a time, through twenty-nine /larapilot-* skills you run in your editor.
Your Laravel app then shows all of it at /larapilot: board, PRD, plan, mockups, Git, and usage,
in the browser.
The agent proposes. You approve what ships. Human-in-the-loop, always.
fernway.test/larapilot
The board of Fernway, a demo app built with Larapilot. Take the tour
Quickstart
Install in 3 commands
Add Larapilot to your Laravel app, scaffold the .larapilot/ workspace, and publish the
/larapilot-* skills via Laravel Boost.
Terminal
1composer require andreapollastri/larapilot --dev
2php artisan larapilot:install
3php artisan boost:install
Check that it worked
✓php artisan larapilot:doctor --human
Open the dashboard
→php artisan serve
→http://127.0.0.1:8000/larapilot
The dashboard is part of your Laravel app: larapilot:install turns it on, and the app you
already run serves it at /larapilot, with no other service to start, no frontend build, and
no account. On Herd or Valet open http://your-app.test/larapilot, on Sail
http://localhost/larapilot. Keep it in a browser tab: each skill writes to
.larapilot/, and the next reload shows it. It answers in local,
development, testing, and staging, never in
production. Every page.
Then open your editor in the project and type /larapilot-: the skills are listed. Pick the
one for your situation from First run.
On Laravel 10/11 the MCP stack uses illuminate/json-schema — stay on a recent framework patch (Laravel 11.47+ recommended on 11.x).
Laravel 10 and 11 are past their security-fix window. Composer 2.9+ refuses every laravel/framework 10.x/11.x release because open advisories have no patched line (fixes shipped in Laravel 12.60+ / 13). Larapilot CI still runs those majors by ignoring only laravel/framework advisories on the 10/11 jobs. Apps still on 10/11 that fail composer update with affected by security advisories need the same ignore (composer config --json policy.advisories.ignore '["laravel/framework"]') or should upgrade to Laravel 12+.
larapilot:install also scaffolds Larastan (level 5+) and Laravel Pint — phpstan.neon.dist, pint.json,
Composer scripts, and dev dependencies. php artisan larapilot:quality runs both; the agent runs
it before every task is marked done, and you run it in CI. See
Code quality.
Already on Boost without a fresh install? Run php artisan boost:update --discover once to pick
up new skills.
Connect the MCP servers
boost:install usually registers them for you. If your editor does not list a
larapilot server, add both by hand — Boost brings Laravel context (docs search, schema,
Tinker), Larapilot brings the workflow (backlog, spec, diagnostics, read-only Artisan):
Verify with php artisan larapilot:doctor. larapilot:update also bumps
laravel/boost to latest stable and republishes skills. Runtime-only refresh:
php artisan larapilot:update --skip-boost.
A new major needs its constraint.composer update stays inside the major
your composer.json names. From 4.x to 5.0, replace step 1 with
composer require andreapollastri/larapilot:^5.0 --dev --with-all-dependencies — see
Version 5 vs version 4.
First run
One command starts the project. Which one depends on what already exists.
Each of the first three ends with a PRD in .larapilot/docs/PRD.md. From there the loop is
always the same: /larapilot-spec builds the backlog, then
plan → implement → review for each story.
Concepts
Ten words, one loop
Larapilot turns a conversation into files in your repository, and those files into reviewed code.
These are the words the rest of this page uses.
Word
What it is
Where it lives
Skill
A slash command you type in the editor, such as /larapilot-plan US-001. It runs a
guided conversation and saves the result. All skills
Published into your editor by Laravel Boost
Persona
A named point of view inside a skill: 💎 Mark on scope, 🔎 Tom on requirements, 🔐 Lars on
security. Lenses on the same work, not separate agents. The
squad
They speak in chat as 💎 Mark:
PRD
Product Requirements Document — what the product promises: users, journeys,
requirements, quality targets, scope, decisions. One per project.
The PRD
.larapilot/docs/PRD.md
JourneyJ-001
One way a user gets value from the product, from the trigger to the result. Stories are cut
from journeys
Inside the PRD
RequirementFR-001
One promise, with a named actor and Done means bullets a tester can verify.
Tagged Must, Should, Could, or Won't (MoSCoW)
Inside the PRD
Quality targetNFR-001
A non-functional requirement: a number and the way to verify it, such as
p95 under 300 ms, checked by k6 in CI
Inside the PRD
StoryUS-001
A spec: one demonstrable piece of the product, with acceptance criteria. It names the journey
and requirements it delivers
.larapilot/specs/US-001.yaml, listed in backlog.yaml
Plan & tasks
The technical plan of one story, cut into TASK-01, TASK-02… Each task
ends in one commit
.larapilot/plans/US-001-plan.yaml
Status
TODO → PLANNED
→ IN PROGRESS →
REVIEW →
DONE. Only skills move a story forward; only you move it
to DONE. Workflow
backlog.yaml
Runtime packs
The rule files skills read before they act. You never edit them;
larapilot:update refreshes them
.larapilot/runtime-*.md
Everything lives in .larapilot/ and is committed with the code. The next session — yours, a
colleague's, or another agent's — starts from the same truth. Every file,
explained. You read those files on the dashboard your Laravel
app serves at /larapilot, not in YAML.
Tour
The dashboard, on a real app
Every picture on this site comes from Fernway, a class-booking app for independent
gyms built for these pages with Larapilot itself: a PRD, sixteen stories in five epics, one release
shipped and one under way, three developers and a product owner, five weeks of commits. The pages
are what /larapilot serves on it — nothing redrawn. Click a picture to see it full size.
fernway.test/larapilot
Board. Sixteen stories by status: three to do, two planned, two in progress, one waiting for review with a blocking comment, eight done with the merge that closed each one. More
fernway.test/larapilot/specs/US-006
Spec detail. US-006 in review, as the spec skill wrote it: header, story, acceptance criteria, then the mockup of the full class, the plan, the tasks, and the comments of the team. More
fernway.test/larapilot/plan
Plan. Release 0.1.0 shipped, so its milestone is done; 0.2.0 is on track for 16 October, with the waitlist in review and two stories under way today. More
fernway.test/larapilot/design
Design. The member booking flow drawn by /larapilot-design, every screen a live preview tagged with the stories it covers. More
fernway.test/larapilot/usage
Usage. Lucille's ledger and Estimate vs build: the hours each delivered story was planned at, against the time it was in progress — read from the backlog, not logged by hand. More
fernway.test/larapilot/economics
Economics. Fernway priced as a subscription by a UK limited company: what the build costs, what one customer leaves each month, how many cover the bills. More
fernway.test/larapilot/database?view=diagram
Database. The seventeen tables of the app as a diagram, foreign keys drawn column to column — laid out on the server, plain SVG, also as a PDF. More
fernway.test/larapilot/logs
Logs. An exception opened on where it was thrown in the application; the nineteen frames of the framework stay one click away. More
fernway.test/larapilot/laravel/mail
Laravel. A booking confirmation as it left the app: who it went to, the mailable that built it, the request that sent it, and the message in a frame that runs no script. More
fernway.test/larapilot/git
Git. Gitflow as the history draws it: stories merged into release/0.2.0 by pull request, two feature branches under way, every commit tagged with its story. More
Light, dark, and a phone
The dashboard follows the system theme, or the switch at the bottom of the sidebar, and every page
works at the width of a phone.
fernway.test/larapilot
The board in dark mode.
The board on a phone.
A spec on a phone.
Usage
How to use
You type a /larapilot-* skill in your editor and answer its questions. The skill writes
its result to .larapilot/ and moves the story to its next status. You never edit those
files by hand, and nothing ships without your review.
A new product — the last three repeat for every story
Inception writes the PRD, spec cuts it into stories, and each story is
planned, built, and reviewed. Walkthrough.
An app already in production, built without Larapilot
php artisan larapilot:install→/larapilot-adopt→/larapilot-spec→the same loop
/larapilot-adopt reverse-engineers the PRD from the code.
Walkthrough.
A product that has a PRD — a request arrives
/larapilot-triage "…"→/larapilot-bug or /larapilot-feature, run for you→the same loop
Triage checks the request against what the PRD and the backlog promised,
states the verdict in one line, and runs the right skill in the same turn.
Walkthrough.
Follow the loop in the browser. You work in the editor; your Laravel app
shows the result at /larapilot. The board moves a story as each skill finishes, the PRD reads
as a document with its decisions, the plan is a Gantt with today on it, and the mockups open as a site you
click through. Read a spec there before you approve it. The
dashboard.
Which skill, when
You have
Type
Starting a project
A new product, site, app, or PHP/Laravel package — or a legacy system to rewrite
/larapilot-inception "…"
A Laravel app in production, built without Larapilot, no PRD yet
/larapilot-adopt
Building, story by story
A PRD and no backlog, or requirements no story covers yet
/larapilot-spec
A story that needs mockups before it is planned optional
/larapilot-design US-XXX
A story in TODO
/larapilot-plan US-XXX
A story in PLANNED
/larapilot-implement US-XXX
A story in REVIEW
/larapilot-review US-XXX
Several stories to plan and build in one run
/larapilot-autopilot US-004 US-005 …
Every story of the release DONE
/larapilot-ship
Changing a product that has a PRD
One new capability, or a shipped one that must now behave differently
Settings are stored in .larapilot/config.yaml and committed, so the whole team works in the
same mode. Secrets and machine-specific paths stay in .env.
Two MCP servers run beside the skills: Laravel Boost gives the agent Laravel context
(docs search, schema, Tinker) and Larapilot gives it the workflow (backlog, specs,
diagnostics, read-only Artisan). See Artisan CLI → MCP server.
Studio
Larapilot for the whole team, in a browser
Larapilot runs where your agent runs: an editor or a terminal. Not everyone on a product team opens one.
Studio puts the same skills, the same backlog, and the same approvals on a server,
behind a web page — for the product manager who decides, the developer who builds, and the client who
follows.
studio.dev.agency.test
Studio. A PM asks for a date filter on the orders. Claude writes US-014 with
larapilot:spec-add, plans it with larapilot:spec-plan, implements it on its
branch, and asks before rebuilding the assets. On the right, the preview of that branch. From the
Studio docs.
The wizard asks for the domain, the DNS record to create, the first administrator, and the git provider,
then installs the newest release of Studio at studio.<your domain>. Larapilot needs
nothing more: install it in the project as the Quickstart says, or let Studio
create the repository with it. Everything else is on
studio.web.ap.it.
Version 5
Version 5 vs version 4
Same skills, same artifacts, same slash commands. What changed is how much an agent reads before it
starts working — and how sure it is to read the rules of your project, those of your frontend
team included.
Context loaded before the work starts
Tokens of skill and runtime files, on a project with default settings
v4
v5
/larapilot-implementfirst skill of a conversation
43k
15k
−65%
/larapilot-planfirst skill of a conversation
44k
17k
−61%
/larapilot-reviewafter implement, same conversation
38k
2k
−94%
triage → bug → plan → implement → reviewone conversation, five skills
173k
31k
−82%
v4 is what a skill was told to read: its packs, whole, every time. v5 is what
larapilot:context lists. Characters ÷ 4; your editor's tokenizer will differ a little, the
ratio will not.
What an agent does differently
v4
v5
Finding the rules
Reads the index, works out which rows apply, reads every part of each pack, then calls
config-show
One call — larapilot:context {skill} — answers settings, paths, the facts of the
project, and the files to read
Which rules
Every value of every setting: three Git modes, three testing bars, every toggle
Compiled for the project: the row its settings call for, and no section for a toggle that is
off. A rule the agent never reads cannot be applied by mistake
The next skill of the conversation
Reads everything again
Reads only what is new — a session token says what is already loaded. --fresh
after a compaction
Heavy packs — tenancy, CI/CD, integrations, UX, deploy
Read up front, whether the spec touches them or not
Named with the moment that calls for them, read then
Logging a session to Lucille
A pack of about 7k tokens, read by every skill for one line of the ledger
The command comes ready in the envelope
spec-list
Every spec with its whole body and history
Code, title, status, priority, epic, and the PRD ids each spec cites —
--full for the rest
The PRD
Opened whole, by nearly every skill
prd-show: the outline, the blocks of some ids, or one section
One rule, one place
Git mode, testing bar, and the delivery rules restated in up to six files
Said once and cited; /larapilot-ship is half its size
Zoey's context estimate
Character counts added up by the agent
A number the CLI computed
Skill by skill
What each skill loads when it is the first of a conversation — its own instructions plus the runtime
files it reads. Later skills of the same conversation pay only for what is new.
Skill
v4
v5
Change
/larapilot-inception
~42k
~21k
−51%
/larapilot-adopt
~45k
~20k
−55%
/larapilot-feature
~38k
~14.5k
−62%
/larapilot-bug
~37k
~10k
−73%
/larapilot-spec
~38k
~13k
−66%
/larapilot-plan
~44k
~17k
−61%
/larapilot-implement
~43k
~15k
−65%
/larapilot-review
~38k
~9.4k
−75%
/larapilot-ship
~36k
~12k
−67%
/larapilot-design
~30k
~14k
−53%
/larapilot-settings
~25k
~10k
−60%
/larapilot-triage
~11k
~6k
−45%
The frontend companion
Version 4 linked a frontend folder and read the package.json at its root. Version 5 reads the
workspace that folder lives in — one app, a monorepo several products share, or one app kept in its own
repository and built inside such a monorepo — and the rules its team wrote for coding agents. Same skills,
same repo: frontend tasks: what changes is what the agent knows before its first frontend
edit.
What the agent knows about the frontend before it writes
Counted in the code of each version's frontend-scan
v4
v5
Workspace layouts readone app, monorepos, an app built inside one
1
11
+10
Kinds of agent rules readAGENTS.md and the formats of 11 editors
0
12
+12
Verification commands givenbuilt from the workspace, non-interactive
0
9
+9
Stack playbooks, by major versionfor what rules and code leave open
0
4
+4
v4 answered a stack label, five folder checks, and the entrypoints of one
package.json. Each v5 number is a list in its scan, not an estimate.
The same repository, scanned by each version
An Angular app kept in a repository of its own and cloned at apps/billing inside the team's Nx
monorepo: a project.json and no package.json, a tsconfig that extends
the monorepo's, the shared UI library imported from there.
Trimmed. v4 saw a folder with no stack and nothing to run. v5 finds the monorepo
among the parent folders, runs the commands there, commits in the app's own repository, and leaves out the
lint target the installed CLI can no longer run.
What the companion does differently
v4
v5
The repository
One folder with a package.json at its root
Nx (the graph of the workspace's own nx, cached), Angular CLI, pnpm / yarn / npm / bun
workspaces, Turborepo, Lerna, Rush, one app — each project with its type, tags, targets, stack,
installed version, and dependencies
A monorepo shared with other products
Read as one app: any folder could be written
The user names this product's projects; libraries are owned, shared (changed only
when a task names them), or vendored (never edited)
An app in its own repository, built inside a monorepo
Not seen: no stack, no commands, no shared libraries
The monorepo is found among the parent folders, or linked with --workspace; commands run
there, commits go to the app's repository
The team's agent rules
Never read: the editor loads the rules of the Laravel workspace only
AGENTS.md at any depth, CLAUDE.md with its imports, Cursor, Copilot, and
eight more formats — those of the monorepo around an app too. frontend-rules --file before
each write; on code, they win
How the code is written
A stack label
Measured on the code — NgModule or standalone, *ngIf or @if, decorators or
signals, <script setup>, Svelte runes — with recent files as models and a playbook
per stack and major version
Checks
npm test, whatever the workspace
The workspace's own commands: nx run, nx affected, ng test
--watch=false with headless Karma, pnpm --filter, … A target the installed CLI can
no longer run is left out and named
Tests the team never wrote
Not noticed
A project with no spec — or generators set to skip them — is a question for the user, recorded in
the decision journal
The API client
Written by hand
A generated client (orval, openapi-generator, ng-openapi-gen, …) is regenerated from the product
OpenAPI, never edited
Commits
feat(US-XXX): TASK-NN … in every repository
The style of the repository's history — types, scopes, language — with the task id in it;
task-done finds the commit in the frontend repository
Who builds the UI
Larapilot, from the Laravel workspace
Larapilot (driven), or the frontend team in its own repository from
frontend-brief (handoff)
Machine paths
Setting the stack copied the local path into the committed config.yaml
New in v5, and off until you turn it on: your own commands and skills on the transitions of the loop —
plan saved, spec started, task done, review, approval, rework, release, ship — written in
.larapilot/hooks.yaml and shared through git. A before hook that fails refuses the
transition and nothing is written; an after hook is reported and the transition stands.
v4
v5, with hooks=YES
A check of the team — tests, Larastan, npm run build
An instruction the agent may forget
A before hook of task.done or spec.review: the command runs it
and refuses the transition when it fails
A deploy
By hand, after the review or the ship report
An after hook of spec.approved for staging, of ship or
release.shipped for production
A custom skill at the right moment
Typed by someone who remembered it
A skill: hook: before a transition the command waits until the agent reports it run;
after one, the answer tells the agent to run it now
A tool Larapilot does not integrate — Teams, a time tracker, a wiki, n8n
A script nobody calls
A run: hook, with the event in LARAPILOT_HOOK_* variables and as JSON on
stdin
Getting past a gate
—
Never by the agent: it fixes what the hook reports, or tells you. --force skips no
hook; LARAPILOT_HOOKS_ENABLED=false turns them off on one machine
A project installed with version 4 holds ^4.x in its composer.json, so
composer update alone stays on 4: the first command raises the constraint to the new major.
larapilot:update then republishes the skills, refreshes the runtime, and removes the runtime
files v5 no longer ships. Nothing in .larapilot/ has to be migrated: PRD, backlog, plans, decisions,
and settings are read as they are. Larapilot under require rather than
require-dev? Drop --dev, or Composer moves it. Six things to check:
spec-list in your scripts — it answers without the bodies now. Add
--full where a script reads body or status_history.
Custom skills — they keep working: the old runtime files are still in
.larapilot/, re-split by audience. To give one the session cache and the compiled rules,
start it with php artisan larapilot:context {its name} --with=delivery-1,dev-docs
(Custom skills).
/larapilot-boogle is /larapilot-error — one skill for every
error tracker. The boogle-* commands still answer under their old names.
An external frontend — run /larapilot-frontend-companion once: in a monorepo
it asks which projects are this product's, and it reads the team's agent rules from then on.
.larapilot/hooks.yaml is new — the update writes it with every example
commented out, and hooks stays NO: nothing runs. Commit it, and turn hooks on when
the team has written one (Workflow hooks).
larapilot:vps-provision is gone — with the provision.sh it
generated: drop the call from scripts and CI. A server it set up is not touched by the upgrade, but its
prj-* tools are no longer maintained: deploy those projects with the platform the PRD records,
through /larapilot-ship.
Laravel 13 greenfield — task board with registration, projects, assignable tasks.
One sentence in, PRD + backlog + code out.
Your editor
/larapilot-inception "I want a simple team task board: registration, projects, and assignable tasks."
Inception is a conversation, not a questionnaire. It goes through five moments, in
this order:
The goal is challenged in plain prose — who has the problem, what changes if it
works, how you will know in 90 days, what would make you stop.
Fixed choices are asked with AskQuestion, at most three per round, all skippable.
Four rounds always happen: Project Kind, Delivery Target, Business Model, Operations & support.
Prior art — with your consent, Sebastian searches for products and packages that
already do it, and you decide: build anyway, adopt, or integrate.
The product is written down — journeys, domain model, requirements with
Done means, quality targets, risks.
Ready check and readback — Tom checks the requirements can be tested, Mark reads
the decisions back, and only then is the PRD saved.
💎 Mark: Before scope — who has this problem today, and what do they use
instead?
You: Small agencies, 2–10 people. They use Trello and lose track of who owns what.
🧭 Jennifer: Then "who owns what" is the product, not the board. How will
you know in 90 days that it worked?
You: Three agencies using it daily, and no task without an assignee.
🏢 Benjamin: Riskiest assumption: that they'll leave Trello. A CSV import
from Trello lowers that cost — I'll propose it as a Should.
AskQuestion — Round 1 (kind, target, money)
Project Kind: Application · Website · Personal · Package
💎 Mark: A SaaS that ships as an MVP — registration, projects and tasks
are Must; the Kanban view and notifications can wait.
💡 Sebastian: Before scope, I would search for what already exists:
"open source team task board laravel" and "self-hosted kanban". Nothing in those
words identifies you. May I?
AskQuestion — prior art (consent)
Search now · Search with generic terms only · Skip — I know the alternatives · Skip —
confidential
You pick:Search now.
💡 Sebastian: Three candidates, written to
research/prior-art.md with license, stack, and last release. The two closest are boards
with no Laravel code to reuse, and neither answers "who owns what" across projects.
AskQuestion — verdict
Build anyway · Adopt / fork · Integrate as dependency · Not checked
You pick:Build anyway.
💎 Mark: Then the difference leads the backlog: no task without an
owner is the first Must.
Ops Owner: Me / my team · Client team · Managed provider · Shared
Support Window: Best effort · Business hours · Extended hours · 24/7
You pick:Managed platform, Me / my team,
Business hours. These three answers price the maintenance retainer in
Economics.
AskQuestion — Round 3 (topology, stack & budget)
Frontend Topology: Laravel-coupled · SPA-in-Laravel · API + external frontend
Authenticated UI (in this repo): Laravel Starter Kit · Filament · AdminLTE ·
Bootstrap 5 · Tailwind CSS · Custom
Budget sensitivity: Tracked · Relaxed
You pick:Laravel-coupled, Laravel Starter Kit,
Tracked.
📐 John + ✨ Joe: Coupled Blade/Inertia UI in this repo — Starter Kit +
session auth fits Laravel 13 out of the box. Filament stays a Future Phase option for ops.
🔎 Tom: Ready check: nine points of ten. FR-003 says "assign a task"
and does not say what happens when the owner leaves the team. I would add: the task returns to the
project owner.
You: Yes.
💎 Mark: Before I write anything — task board for small agencies ·
core journey assign and follow a task · MVP, sold as SaaS · prior art: build anyway · Must:
registration, projects, assignment with an owner always set · riskiest assumption: agencies leave
Trello · nothing left undecided.
AskQuestion — readback
Write the PRD · Revise
You pick:Write the PRD.
PRD.md (excerpt)
## User Journeys
### J-001: Assign and follow a task _(core journey)_
**Persona:** Agency lead · **Trigger:** client work arrives · **Frequency:** daily
**Steps:** 1. open the project 2. create the task 3. pick the owner 4. the owner sees it on their list
**Success end-state:** the task has exactly one owner and appears on that owner's list
**Failure modes:** the owner left the team · the task has no project
**FRs:** FR-002, FR-003 · **MoSCoW:** Must
## Domain Model
| Entity | What it is | Key states / lifecycle | Relations | Owner persona |
| --- | --- | --- | --- | --- |
| Task | A unit of work | Open → In progress → Done | belongs to Project, has one owner | Agency lead |
## Functional Requirements
### FR-003: Assign a task to a team member
**MoSCoW:** Must · **Journey:** J-001 · **Persona:** Agency lead
**Actor & trigger:** the agency lead, from a project, when work is handed over
**Behavior:** every task has exactly one owner, chosen among the members of its project
**Done means:**
- a task cannot be saved without an owner
- when an owner leaves the team, their open tasks return to the project owner
- the owner sees the task on their list at the next page load
**Out of this FR:** several owners, watchers (Future Phases)
**Depends on:** FR-002 · **Source:** interview
### FR-004: SSO via Google Workspace
**MoSCoW:** Won't
## Non-Functional Requirements
| ID | Category | Target | Applies to | Verified by |
| --- | --- | --- | --- | --- |
| NFR-001 | Performance | p95 < 300 ms on task lists at 5k tasks | J-001 | k6 run in CI |
| NFR-002 | Accessibility | WCAG 2.2 AA | all UI | Lighthouse + axe |
## MVP Scope
**Project Kind:** Application
**Delivery Target:** MVP
**Business Model:** SaaS subscription
**Prior Art:** Build anyway
**Success signal:** three agencies using it daily; no task without an owner
## Risks & Assumptions
**Riskiest assumption:** agencies will leave Trello
**Kill condition:** no agency uses it daily after 90 days
## Technical Architecture
**Server Management:** Managed platform
**Ops Owner:** Me / my team
**Support Window:** Business hours
**Frontend Topology:** Laravel-coupled
**Stack:** Laravel 13 · Laravel Starter Kit · Pest
Tom cuts one story per journey. Its acceptance criteria come from the Done means of the
requirements it delivers, plus the quality targets that apply. Won't requirements get no
story.
backlog.yaml (excerpt)
specs:
- code: US-001
title: User Registration
status: TODO
- code: US-002
title: Project Management
status: TODO
- code: US-003
title: Task Assignment
status: TODO
Your editor — optional
/larapilot-design US-001
Static HTML mockup from the PRD-chosen packaged design system (design-systems/filament/,
starter-kit/, bootstrap-5/, tailwind/, or adminlte/).
Preview at /mockups/US-001, on the spec detail page, and in the dashboard
Design index (/larapilot/design) — navigable walk through every screen,
plus a zip of HTML + assets.
User model, RegisterController, Blade view, Pest tests, and the domain doc
.larapilot/docs/devs/user-registration.md
/larapilot-review US-001
REVIEW → DONE
You approve — or send back with feedback
Laravel 13 app (US-001)
database/migrations/xxxx_create_users_table.php
app/Models/User.php
app/Http/Controllers/RegisterController.php
app/Http/Requests/RegisterRequest.php
resources/views/auth/register.blade.php
routes/web.php
tests/Feature/RegistrationTest.php
.larapilot/docs/devs/user-registration.md # why it is built this way — always English
Story
Prompts
What gets implemented
US-002 Projects
plan → implement → review
Project model, CRUD, policies, Pest tests
US-003 Tasks
plan → implement → review
Task model, assignment, board view, tests
Your editor — optional batch
/larapilot-autopilot US-002 US-003
Chains plan + implement, one spec at a time. Under STANDARD or MAX, plan and
implement Phase 1 each run in a fresh sub-agent; this session keeps CLI transitions, questions, and the
Robert/Lars review. ECO stays inline. You still run /larapilot-review per spec
unless auto_approve is YES.
Your editor — when all MVP stories are DONE
/larapilot-ship
You do
Answer wizard rounds; run spec loop per story; approve reviews
A 2-year-old Laravel 11 SaaS in production — no PRD, no backlog, built straight
from tickets. You want the Larapilot loop from here on, without rewriting anything. The
code is the source of truth; adopt reconstructs the product spec from it.
No idea to type — the agent reads the repository. Tom, John, Mike and Andrew map the code; Mark frames it
as a product; you only answer what the code can't tell them.
🔎 Tom: Scanned the repo. 14 models, 9 policies, 22 routed controllers,
6 queued jobs, a Cashier subscription flow, and a Filament admin panel. I've written every observed
behavior into .larapilot/research/codebase-analysis.md with file-path evidence.
💎 Mark: From shipped behavior I can infer the personas (Account owner,
Team member, Admin) and ~26 functional requirements. Three things the code doesn't settle:
AskQuestion — Round 1 (gaps the code can't answer)
Delivery target for the reconstructed PRD: MVP · V1 Complete · Full Product ·
Enterprise
app/Support/LegacyImport/ — still in use? Active feature ·
One-off migration (mark deprecated) · Unsure
Two half-built controllers behind a feature flag: In scope (document as planned) ·
Out of scope (note as spike)
You pick:V1 Complete, One-off migration,
Out of scope.
📐 John: Architecture, data model and package inventory captured. Andrew
flagged 3 spots that fight Laravel idiom — noted as tech-debt candidates, not blockers.
The PRD records Project Origin: Adopted (existing codebase) — downstream skills scope it
like Greenfield (Sabrine stays silent, no legacy parity contract).
## Routed surface
| Method | URI | Controller | Auth | Notes |
| --- | --- | --- | --- | --- |
| GET | /dashboard | DashboardController@index | auth, verified | team-scoped |
| POST | /projects | ProjectController@store | auth, can:create,Project | |
| POST | /billing/checkout | BillingController@checkout | auth, can:manage-billing | Cashier |
## Inferred requirements (evidence)
- FR-004 Team members can be invited by email — `InviteController`, `InvitationMail`, `invitations` table
- FR-011 Billing is per-seat monthly — `Team::subscription()`, `config/cashier.php`, `PlanSeeder`
- FR-019 Admins can suspend a team — `Admin/TeamController@suspend`, `Team::$casts['suspended_at']`
PRD.md (excerpt)
**Project Origin:** Adopted (existing codebase)
**Delivery Target:** V1 Complete
**Project Kind:** Application
### In Scope (reconstructed from shipped behavior)
- FR-001…FR-026 — see analysis report for per-FR file evidence
### Known gaps / not reverse-engineered
- Two feature-flagged controllers — spike, out of scope (your call)
- `app/Support/LegacyImport/` — deprecated one-off, do not extend
You do
larapilot:install, then /larapilot-adopt; answer only the gap questions
Agent does
Reads the whole codebase → codebase-analysis.md + a complete PRD via
prd-write / validate-prd
Next step
/larapilot-spec to build the backlog, then the normal
plan → implement → review loop
Laravel package
/larapilot-inception · Project Kind Package
First-class Composer package workflow — provider, public API, Pest matrix, CI,
semver, Packagist/Satis. UI mockups stay off unless the package ships Blade/Livewire/Filament.
Your editor
/larapilot-inception "Laravel package that adds signed webhook delivery with retries and a publishable config."
💎 Mark: Package kind — greenfield or wrapping existing code?
AskQuestion — Package origin & delivery
Project Kind: Application · Website · Personal · Package
Package Origin: New · Existing local · Existing git
Delivery target: MVP · V1 Complete · Full Product
You pick:Package, New, MVP.
👾 Andrew + 📐 John: Namespace, Service Provider, config publish, and public
API surface before backlog.
🗄️ Mike: Optional migrations only if the package owns schema; otherwise
document host-app tables.
⌨️ Sarah: CI matrix (PHP/Laravel) + release scripts; Artisan doctor/publish
commands when useful.
📒 Lucille: Any Packagist / demo deadline? (skippable)
PRD.md (excerpt)
**Project Kind:** Package
**Package Origin:** New
**Delivery Target:** MVP
### Package
**Name:** acme/webhooks
**Distribution:** Packagist
**Consumer install:** composer require acme/webhooks
### FR-001: Publishable config + Service Provider
**MoSCoW:** Must
### FR-002: Signed outbound webhook client with retries
**MoSCoW:** Must
### FR-003: Pest feature tests + GitHub Actions matrix
**MoSCoW:** Must
Then the same loop: /larapilot-spec (package-surface stories first) → plan → implement →
review → /larapilot-ship with Packagist/semver checklist. Skip
/larapilot-design unless the package ships UI.
You do
Confirm origin, name, distribution, and consumer install mode
On /larapilot-spec, Tom bootstraps migration specs first.
specs/US-001.yaml (excerpt — first story)
code: US-001
title: "Legacy schema analysis and ETL plan"
priority: HIGH
status: TODO
body: |
#### US-001: Legacy schema analysis and ETL plan
**Epic:** EP-001 Legacy migration | **Priority:** HIGH | **Points:** 5
**Type:** Migration | **Traces to:** Legacy parity (anagrafica + ordini)
**User Story**
As a developer,
I want a documented ETL plan from legacy MySQL to Laravel,
so that customer and order data migrates without loss.
**Acceptance Criteria**
- [ ] Field-level mapping `clienti` → `customers`, `ordini` → `orders`
- [ ] Idempotent import command with dry-run
- [ ] Parity matrix rows updated with test evidence
- [ ] Sabrine signs off before US-002 (CustomerResource UI)
You do
Drop legacy snapshot in .larapilot/legacy/; invoke inception; answer origin + scope
rounds
Agent does
Writes PRD + legacy-parity.md; /larapilot-spec with migration specs first
Invoicing SaaS shipped — stakeholder wants PDF export on invoice detail.
Your editor
/larapilot-feature "Add PDF export for invoices"
Mark and Tom read the PRD and the backlog, confirm nothing already covers it, then ask three short
rounds. Nothing is saved until you confirm the readback.
💎 Mark: Got it — PDF export for invoices from the detail page.
Let's align scope and priority.
AskQuestion — Round 1 (scope & priority)
MoSCoW: Should · Must · Could
Traces to: New FR-011 · Extends FR-004 (Invoicing) · Changes FR-004 ·
Standalone enhancement
Journey and persona: J-002 Invoice a client (Freelancer) · New journey ·
None
You pick:Should, New FR-011,
J-002.
👾 Andrew: Prior art for the feature: spatie/laravel-pdf
renders a Blade view to PDF and is maintained. No reason to build a renderer.
AskQuestion — Round 2 (delivery shape)
Complexity: Small (1 spec) · Medium · Large (split epics)
Mockup first? Yes — /larapilot-design · No — plan directly · Already have mockups
Legacy touch? No · Maps to legacy parity · Needs scraping/porting
You pick:Small, No — plan directly, No.
AskQuestion — Round 3 (backlog placement)
Priority: HIGH · MEDIUM · LOW · CRITICAL
Epic: EP-002 Invoicing · New epic · Other
Blocked by: None · US-004 (Invoice detail UI)
You pick:MEDIUM (Should), EP-002 Invoicing,
US-004.
🔎 Tom: Acceptance criteria, from what FR-011 will promise:
The PDF downloads from the invoice detail page
403 when the user cannot view that invoice
The PDF carries line items, tax breakdown, and the tenant logo
NFR-001 applies: the download starts within 300 ms at p95
💎 Mark: Readback — new FR-011 Export an invoice as PDF,
Should, journey J-002 · four criteria · the PRD gains FR-011 and one history row · story in EP-002,
MEDIUM, after US-004.
AskQuestion — readback
Add to backlog · Revise
You pick:Add to backlog.
Mark adds FR-011 to the PRD with a row in its revision history, then the story
US-011 is created.
specs/US-011.yaml (excerpt)
code: US-011
title: "Export invoice as PDF"
priority: MEDIUM
status: TODO
body: |
#### US-011: Export invoice as PDF
**Epic:** EP-002 | **Priority:** MEDIUM | **Points:** 3 | **Status:** TODO
**Blocked by:** US-004
**Type:** Feature
**Traces to:** J-002 · FR-011 (MoSCoW: Should) · NFR-001
**Prior art:** package spatie/laravel-pdf — maintained, renders Blade
**User Story**
As a freelancer,
I want to download a PDF of an invoice from its detail page,
so that I can archive or send it to clients offline.
**Acceptance Criteria**
- [ ] PDF download from invoice detail for authorized users
- [ ] 403 when the user cannot view the invoice
- [ ] PDF contains line items, tax breakdown, and tenant logo
- [ ] Download starts within 300 ms at p95 (NFR-001)
The feature changes something that already works
That is a change request: the product does what was agreed and you now want it
different. The requirement is edited in place — same id, new behavior — and the
revision history records before and after. The story that shipped the old behavior stays
DONE; the new story cites it as Supersedes.
Rules: How the PRD changes.
You do
Describe the feature in one line; answer three rounds; confirm the readback
Agent does
Checks for packages that already do it, writes the criteria, adds FR-011 to the
PRD, saves story US-011
Next step
/larapilot-plan US-011
Bug resolution
/larapilot-bug
SSO works in Chrome, fails in Safari — maps to done story US-003.
Your editor
/larapilot-bug "SSO login fails on Safari"
Sophia triages with AskQuestion and logs to support/intake.md. Before the first
question she reads the logs of the application — every time, whatever the report says:
a defect told in words is often already written there, with its exception, its file, and its line.
larapilot:logs gives them as entries with the
repeats counted and the secrets redacted, so nothing private lands in chat or intake.
🎧 Sophia: Report received — SSO works on Chrome, fails on Safari.
Reading the logs first.
🎧 Sophia: One thing stands out: InvalidStateException at
app/Http/Controllers/SsoController.php:41, 38 times in three days, the last an hour ago.
That goes into intake and into the fix spec. Now let's classify and route.
AskQuestion — Round 1 (severity & environment)
Severity: Critical · High · Medium · Low
Environment: Production · Staging · Local · Unknown
Security-related? Yes · No · Unsure
You pick:High, Production, Unsure
(auth boundary — Lars will review).
🎧 Sophia: Production auth failure — the logs name the place; the
health of the runtime rules out the infrastructure.
Agent — the health of the runtime
php artisan larapilot:diagnostics --no-logs
🎧 Sophia: Checks look healthy (database /
storage_writable ok), so it is the code: the OAuth state does not survive the redirect
on Safari.
AskQuestion — Round 2 (reproduction)
Reproducible? Always · Sometimes · Once · Not yet tried
Maps to existing spec? Yes — US-003 · No — new fix spec · Unsure
You pick:Always (Safari 17+), Auth / SSO,
Yes — US-003.
AskQuestion — Round 3 (routing)
Preferred path: Rework existing spec · New fix spec · Log only
Urgency: Hotfix now · Next sprint · Backlog
You pick:New fix spec (US-003 is
DONE and stays closed), Next sprint (High but
not Critical — no hotfix/* branch).
🔎 Tom: Promise broken: nothing written — FR-003 promises SSO
login and names no browser. That is a requirement gap, and the fix needs a sentence to be measured
against.
The command answers with what the file holds, then the entries: when, what, and
where — a path of the server read as a file of the repository — with the frames of the
application and not the sixty of the framework under them. The logs are the ones of the machine the
command runs on: env says which environment wrote them, and nothing logged is an answer too.
Full reference: Diagnostics and logs.
Sophia appends a normalized entry to the support log, with what the logs said:
.larapilot/docs/support/intake.md (excerpt)
## BUG-20260715-sso-safari
- **Reported:** 2026-07-15
- **Severity:** High
- **Environment:** Production
- **Summary:** SSO login fails on Safari; works on Chrome
- **Logs:** InvalidStateException at app/Http/Controllers/SsoController.php:41 ×38, last 2026-07-15 08:14:02 (production)
- **Steps to reproduce:**
1. Open app in Safari 17+
2. Click "Sign in with Google"
3. Complete OAuth — redirect returns to /login with error
- **Expected / Actual:** Expected dashboard · Actual login error
- **Promise broken:** none — requirement gap (FR-003 names no browser)
- **Affected spec:** US-003 (DONE)
- **Routed to:** spec-add US-015, related US-003
- **Security:** unsure — Lars/Oliver tagged
- **Occurrences:** 2026-07-15 Production
US-003 shipped and stays closed: the skill creates fix spec US-015 and relates
it to the story that shipped the behavior. Because nothing ever named a browser, the PRD gains the
sentence the fix is measured against — a row in the compatibility NFR and a done-means bullet under
FR-003, never an FR-012: Fix Safari.
specs/US-015.yaml — body (excerpt)
#### US-015: Fix — SSO login fails on Safari
**Type:** Fix · **Severity:** High · **Security:** Lars/Oliver — unsure
**Related:** US-003
**Traces to:** J-001 · FR-003 · NFR-007
**Promise broken:** none written — FR-003 now reads "SSO login succeeds on Safari 17+, macOS and iOS"
**Steps to Reproduce**
1. Safari 17+ → Sign in with Google → redirect returns to /login with an error
**Acceptance Criteria**
- [ ] SSO login succeeds on Safari 17+ (macOS and iOS)
- [ ] SameSite / session cookie behaviour verified for the OAuth callback
- [ ] Regression test covers the Safari user-agent and fails without the fix (Anne)
- [ ] Root cause named in the fix commit; auth developer doc updated
- [ ] Lars/Oliver review if the cookie or state token issue is confirmed
Which route, by the status of the story
The defect maps to a story in
Route
REVIEW
spec-request-changes US-XXX — back to TODO with the feedback. It is the only status
that command accepts
TODO · PLANNED
The story is re-issued through spec-add with the criteria added — same code, status
kept. A planned one is planned again
IN PROGRESS
You are told work is under way; on your consent the criteria join the story and the next task
DONE
New fix spec, Type: Fix, related to the story. A done story is never reopened
No story at all
New fix spec. Critical in production → Jack's hotfix/* note
You do
Describe the defect; answer the triage wizard
Agent does
Reads the logs every time (larapilot:logs, secrets redacted); cites what they
say in support/intake.md; routes rework or fix spec
Next step
/larapilot-plan US-015
Bug or feature?
/larapilot-triage → /larapilot-bug or
/larapilot-feature
Invoicing SaaS shipped. A client email lands: they want the company logo on the
invoice PDF. It reads like a wish — is it one?
Your editor
/larapilot-triage "We would like the company logo on the invoice PDF"
Triage is the front door. It does not interview you: it measures the request against what the product
promised — the backlog and the PRD — and says which skill owns it. Wording is a hint, never
the verdict.
The promise test
Verdict
When
Evidence cited
Runs
Bug
An FR or an acceptance criterion promises it, or it worked before
FR-XXX done-means, US-XXX criterion, or the release where it
worked
/larapilot-bug
Bug — requirement gap
Nothing written, but an FR covers the area and any user would take the behavior for granted
(browser, locale, data kept, permission respected)
Parent FR-XXX
/larapilot-bug
Feature
No FR covers it — a new capability, or an extension of one
Nearest FR-XXX, or none
/larapilot-feature
Feature — change request
It works as specified and you now want it different
The FR or criterion that describes today's behavior
/larapilot-feature
Tom and Mark look the request up: spec-list titles, the outline prd-show
gives of the PRD, then at most one spec and one FR. The whole PRD is never loaded.
Agent — evidence
php artisan larapilot:context triage # settings, paths, the core files only
php artisan larapilot:spec-list # the backlog without the bodies
php artisan larapilot:prd-show # outline: every FR with its title
php artisan larapilot:spec-show US-011 --fields=id # body + criteria, tasks reduced to ids
🎧 Sophia: Bug — US-011 criterion "tenant logo in PDF" not met →
larapilot-bug
One line, with its evidence. US-011 is DONE and one of
its criteria reads “PDF contains line items, tax breakdown, and tenant logo”: the request was
phrased as a wish, but the product already promised it. Triage then runs /larapilot-bugin the same turn — you do not type a second command — and passes what it found:
Triage handoff (carried into the target skill)
Triage handoff
request: We would like the company logo on the invoice PDF
verdict: Bug
evidence: US-011 criterion "PDF contains line items, tax breakdown, and tenant logo"
maps to: US-011 (DONE)
settled by: evidence
Sophia's bug interview starts with Maps to existing spec? already answered, and does not
ask you to describe the problem again. From here it is the bug walkthrough:
severity, environment, intake log, and a fix story related to US-011 — which is
DONE and stays closed.
Same product, three other requests
Request
Evidence
Verdict
“Invoice export to CSV does not work”
No FR and no criterion mentions CSV — FR-011 is PDF only
Feature — reported as broken, never promised.
/larapilot-feature starts with Traceability answered
“The PDF should open in the browser instead of downloading”
US-011 promises a download, and delivers it
Feature — change request. /larapilot-feature edits
FR-011 instead of adding an FR
“Login fails on Safari”
FR-003 promises SSO login; no browser list anywhere
Bug — requirement gap. /larapilot-bug clarifies
FR-003 — see How the PRD changes
When the evidence does not settle it
One question, never a round of them. Severity, MoSCoW, priority, epic, and release are asked later, by the
skill that owns them.
🎧 Sophia: “Reminder emails arrive late” — FR-007 promises reminders, no
timing anywhere. Your call.
AskQuestion — verdict (skippable)
Bug — it should already work
Feature — it is new, or a change to what was agreed
You pick:Bug — it should already work. Skipped instead? An FR covers the area
→ Bug — requirement gap; no FR covers it → Feature.
Your answer goes to the decision journal — it is the record of who
decided that a report was a defect and not new scope:
“Login is broken on Safari, and we'd like CSV export too.” Sophia lists the two, classifies each, and hands
off one at a time — bugs before features, the most severe bug first — coming back to the next when the
target skill reaches its next steps.
Neither a bug nor a feature
No handoff: triage says where the request belongs in one line and stops.
Request
Exit
No PRD and no backlog
/larapilot-adopt when the code exists, otherwise
/larapilot-inception
Redefines the product vision or the target user
/larapilot-inception
Changes the PRD: priority, scope, a decision
/larapilot-prd
An open spec already covers it, and nothing is broken
Names US-XXX and its status. No new spec
Several stories for the backlog
/larapilot-spec
A question, a how-to, or a project setting
Answers it, or /larapilot-settings. No spec
Called the wrong skill directly?
/larapilot-bug and /larapilot-feature run the same check on their own evidence.
A “bug” nothing ever promised is handed to /larapilot-feature; a “feature” an FR already
promises is handed to /larapilot-bug. Once, on new evidence only, and never against a verdict
you settled.
What it costs
Triage reads the every-skill runtime rows and nothing else — not runtime-ops, not
runtime-discovery — so the branch it does not take costs nothing. The target skill loads its
own packs, does not re-read what triage loaded, and closes the session with one Zoey line and one Lucille
ledger entry that cover both.
You do
Paste the request as it arrived; answer one question only when the evidence is not enough
Agent does
Cites the FR or criterion, states the verdict in one line, runs the right skill with the handoff.
Writes no spec, PRD edit, or intake entry itself
Next step
None to type — you are already in /larapilot-bug or /larapilot-feature
PRD revision
/larapilot-prd
Invoicing SaaS, PRD written three weeks ago, fourteen stories in the backlog.
Nothing is broken and nothing new is wanted — but the client dropped the CSV export, wants 2FA mandatory
for admins, and nobody ever said what "fast" means.
Your editor
/larapilot-prd "Drop the CSV export, make 2FA mandatory for admins, and say what 'fast' means"
This is neither a bug nor a feature: the product will promise fewer things, in a different order, and
one of them more precisely. That is a revision. The skill does four things, in this
order, and writes nothing before you confirm.
1 · Name what kind of change it is
💎 Mark: Re-scope — FR-009 Should → Won't, FR-006 Could → Must · Sharpen —
NFR-001. Dropping the export frees US-014; accountants keep the integration in FR-011.
🔎 Tom: FR-006 has to say what mandatory means. Two done-means: an admin
without 2FA cannot reach the panel; recovery codes are shown once.
📐 John: "Fast" — p95 under 300 ms on list pages at 10k rows per tenant,
verified by a k6 run in CI. Is that the number, or do you have one from the client?
You: That number is fine.
The team asks only what it cannot work out. When you do not have an answer, it is written as an open
question with an owner — never as an invented number. The seven kinds of revision are listed in
The PRD → Revision kinds.
Three stories cite the ids being changed. What happens to each depends on how far it has gone — a
story in TODO is simply rewritten, a
DONE one is never reopened. The full table is in
The PRD → The backlog follows.
3 · Read the change back
Mark, in chat — then AskQuestion: Apply · Revise · Cancel
FR-009 Export CSV Should → Won't (Retired 2026-08-02 — replaced by the accountant integration)
FR-006 2FA for admins Could → Must · done-means +2
NFR-001 Performance "fast" → p95 < 300 ms at 10k rows, verified by k6 in CI
Backlog US-014 TODO → delete · US-009 PLANNED → update and replan · US-003 DONE → change request
You pick:Apply.
4 · Write the PRD and align the backlog
PRD.md after the revision (excerpt)
### FR-009: Export invoices as CSV
**MoSCoW:** Won't · **Journey:** J-003 · **Persona:** Accountant
**Retired:** 2026-08-02 — replaced by the accountant integration (FR-011)
## PRD Revision History
| Date | Trigger | Summary |
| --- | --- | --- |
| 2026-07-01 | larapilot-inception | Initial PRD |
| 2026-08-02 | larapilot-prd — Re-scope + Sharpen | FR-009 retired; FR-006 Could → Must (+2 done-means); NFR-001 p95 < 300 ms |
FR-009 is retired, not deleted: its heading stays, so every story, commit, and document that cites
FR-009 still points somewhere. Then, with your consent, US-014 is deleted,
US-009 is rewritten with the new target, and US-003 stays closed.
Two other ways to use it
Situation
Type
What happens
You edited PRD.md by hand
/larapilot-prd "I edited the PRD by hand"
It reads the git diff of the file, names the kind of change, and adds what a hand edit
skips: the history row, validation, the dashboard snapshot, the backlog check
The PRD was written by an older Larapilot and validate-prd shows warnings
/larapilot-prd "Upgrade the PRD"
It builds the missing sections from what exists — journeys from the stories, the domain
model from the requirements and the code — and marks each item
derived — confirm. What nobody knows becomes an open question
You do
Say what should change; answer only what the team cannot work out; confirm the readback
Agent does
Names the kind, finds the stories affected, edits the PRD with a history row, validates,
aligns the stories by status
Next step
/larapilot-plan US-009 · /larapilot-feature "Enforce 2FA on existing admin
accounts"
Booking SaaS: Laravel API + an Angular app inside the company's Nx monorepo,
next to the apps of other products. Laravel is the only Larapilot cockpit — the monorepo is
a linked write target with its own rules. Reference:
External frontend repo.
1 · Inception (Laravel)
Laravel editor
/larapilot-inception "Clinic booking API — React SPA in a separate repo"
John + Joe ask Frontend Topology → you pick API + external frontend,
Angular, and provide the absolute path when asked (stored in
LARAPILOT_FRONTEND_REPO_PATH). Filament for ops admin on Laravel. PRD records stack, projects,
API + OpenAPI as Must — not the machine path.
frontend-set writes LARAPILOT_FRONTEND_REPO_PATH in .env.
frontend-scan sees an Nx workspace with 40 projects and asks which are this product's; you name
clinic and clinic-admin. The next scan knows Angular 19, pnpm, the libraries the
clinic apps own and the ones other teams share, the team's AGENTS.md and Cursor rules, that
components are standalone with signal inputs and @if, and that the API client is generated with
orval.
API stories touch Laravel. UI stories use plan tasks with repo: frontend and
project: clinic — implement reads the team's rules first, asks frontend-rules
which ones govern each file, regenerates the orval client after the API task, writes under
apps/clinic/… the way the code already is, runs nx run clinic:test and
nx affected, and commits with the repository's hooks on. With the frontend team building
itself, frontend-set --mode=handoff turns implement into the backend tasks plus
frontend-brief US-003. Mockups stay in Laravel .larapilot/mockups/.
You do
Work only in the Laravel workspace — set path, spec, plan, implement
Agent does
Scans FE · follows its rules · plans BE + FE tasks · writes both repos from Laravel
Next step
/larapilot-spec → implement Must stories
Tracker sync
/larapilot-tracker
Invoicing SaaS, already in delivery: 12 specs in the backlog, four of them
planned. The client's PM runs everything from Linear and will never open
backlog.yaml. You mirror the backlog into their team so they can follow progress, and read
status back before standup. Full reference:
Project trackers.
1 · Point Larapilot at the tracker
Editor
/larapilot-tracker
🔗 Matt: No provider configured yet — 12 specs, 0 linked. Let's pick the
destination first.
AskQuestion — Project tracker
Where should the backlog be mirrored: Linear · Jira · Asana · ClickUp · Trello ·
Monday
You pick:Linear — issues on a team, plan tasks as sub-issues.
🔐 Lars: I need a personal API key (Linear → Settings →
API) and the team key — the short prefix on your issues, e.g. ENG. The key
goes in .env, never in .larapilot/: that directory is committed.
The real push then fails — and this is the common first-run outcome, not an edge case:
Terminal
php artisan larapilot:tracker-push # exit code 3
Envelope (excerpt)
{
"kind": "error",
"error": {
"code": "E_CONNECTOR",
"message": "12 of 12 stories failed to sync.",
"details": {
"errors": [{
"code": "US-001",
"message": "Linear team ENG has no workflow state named \"Todo\". Existing states: Backlog, In Progress, In Review, Done. Rename the state or adjust larapilot.tracker.providers.linear.status_map."
}, … ]
}
}
}
🔗 Matt: The client's team calls its first column Backlog,
not Todo. Larapilot will not create a column in someone else's tracker — two ways out: rename
the state in Linear, or point the map at what already exists. I'd take the second: it's their board.
In Linear the client now sees ENG-42 — US-001 — Staff can issue an invoice, sitting in
Backlog, carrying the spec body, priority, points as the estimate, and the epic — with
four sub-issues, one per plan task. Larapilot writes the mapping to disk:
Commit tracker.yaml. It is how the team shares one
mapping — without it, your colleague's first push creates a second ENG-… for every story. It
holds identifiers only, never the API key.
4 · Normal delivery — the push is cheap
You keep working as usual: /larapilot-plan, /larapilot-implement,
/larapilot-review. Re-push whenever you like — unchanged stories are fingerprinted and
skipped without an API call.
Terminal — after planning US-005 and finishing two tasks on US-003
The removed subtask is a plan task that disappeared when you re-planned US-005 — the matching
sub-issue is deleted rather than left orphaned in the client's board.
5 · Before standup — read the drift
Overnight the PM dragged two cards. tracker-pull with no flags changes nothing
— it reports.
Terminal
php artisan larapilot:tracker-pull
Envelope (excerpt)
"apply": false,
"stories": [
{ "code": "US-003", "ref": "ENG-44", "local_status": "IN PROGRESS",
"remote_status": "In Review", "suggested_status": "REVIEW",
"drift": true, "applied": false, "blocked": null },
{ "code": "US-007", "ref": "ENG-48", "local_status": "REVIEW",
"remote_status": "Done", "suggested_status": "DONE",
"drift": true, "applied": false,
"blocked": "Remote status maps to DONE. Approve through /larapilot-review or larapilot:spec-approve so the merge commit is recorded." },
{ "code": "US-002", "ref": "ENG-43", "local_status": "PLANNED",
"remote_status": "Backlog", "drift": false, "applied": false } ],
"summary": { "checked": 12, "in_sync": 10, "drifted": 2, "applied": 0, "missing": 0 },
"hint": "2 stories drifted. Re-run with --apply to write the mapped statuses into the backlog."
🔗 Matt: Two drifts. US-003 genuinely moved to review — safe
to apply. US-007 the PM marked Done, which Larapilot will not accept from a
tracker.
💎 Mark: Note US-002: local PLANNED, remote
Backlog. Both TODO and PLANNED map to that column, so it is
in sync, not drift — you won't get a false alarm every morning.
Terminal
php artisan larapilot:tracker-pull --apply
Envelope (excerpt)
"summary": { "checked": 12, "in_sync": 10, "drifted": 2, "applied": 1, … },
"hint": "Statuses that map to DONE were left alone — approve them through /larapilot-review."
US-003 is now REVIEW in backlog.yaml. US-007 is
untouched — you finish it the normal way:
Editor
/larapilot-review US-007
What pull will never do
Change made in Linear
Effect on .larapilot/
Why
Card moved to In Review
Applied with --apply
Status is the one thing the tracker legitimately observes
Card moved to Done
Reported, never applied
DONE is a human gate that records the merge commit —
spec-approve owns it
Issue title or description edited
Ignored, overwritten on the next push
Spec text is owned by .larapilot/; the card footer says so
Card moved to a state outside the map
Drift with no suggestion
Better a visible unknown than a guessed status
Issue deleted
Flagged missing; next push recreates it
The link is stale, not the spec
Comment added
Imported once as non-blocking internal feedback
Opt-in via LARAPILOT_TRACKER_PULL_COMMENTS=true
6 · Keep it fresh (optional)
.github/workflows/tracker.yml (excerpt)
- name: Sync backlog to Linear
run: php artisan larapilot:tracker-push
env:
LARAPILOT_TRACKER_ENABLED: true
LARAPILOT_TRACKER_PROVIDER: linear
LARAPILOT_LINEAR_API_KEY: ${{ secrets.LARAPILOT_LINEAR_API_KEY }}
LARAPILOT_LINEAR_TEAM: ENG
Run it on the default branch after merges. Because unchanged stories cost no API call, a push on every
merge is cheap.
The tracker is a window, not a second workflow. Scope still changes
through /larapilot-feature and /larapilot-prd, plans through
/larapilot-plan, and acceptance through /larapilot-review. What the client gets is visibility in the tool they
already use — not a second place to run delivery from.
Your own skill
/larapilot-custom-skill → /acme-predeploy-gate
An agency runs a dozen Laravel apps. Before every production deploy the lead
goes through the same checks by hand: nothing half-done in the backlog, install healthy, Pint and
Larastan green, security scan clean — and every few weeks one gets skipped. You turn that checklist into
a slash command the whole team runs the same way, built on Larapilot's own CLI. Reference:
Custom skills.
1 · Describe the ritual
Your editor
/larapilot-custom-skill "A pre-deploy gate we run before every production release: nothing left in review, doctor healthy, quality green, security scan when it's on, then GO or NO-GO"
Zoey interviews, Albert tightens the wording, Sarah persists. Before asking anything the skill reads the
project and checks for name clashes:
🤖 Zoey: No custom skills in .larapilot/skills/ yet. Three
short rounds, then I show you the draft before anything is saved.
AskQuestion — Round 1 (intent)
What are we building: One new custom skill · Several related skills (shared
prefix) · List existing custom skills only
You pick:One new custom skill.
AskQuestion — Round 2 (trigger)
Slash name:acme-predeploy-gate · predeploy-gate ·
Other
When should the agent reach for it? — becomes the YAML
description Boost routes on
You pick:acme-predeploy-gate, and describe it: "Before a production
release. Use when someone says deploy, go live, release to production, or asks whether main is safe to
ship."
🤖 Zoey: Good trigger — it names the words people actually type. The
acme- prefix keeps it clear of the packaged larapilot-* names.
AskQuestion — Round 3 (workflow)
Personas: Jack + Lars + Anne · Jack only · Other
Runtime packs: Every-skill core only · + runtime-ship.md ·
Other
CLI commands, in order: list them in chat after this round
You pick:Jack + Lars + Anne, Every-skill core only, and
list the checks: backlog, doctor, quality, checkpoint when the scan is on, a scope line, notify.
📝 Albert: Numbered the steps, one line per check, and a hard stop on
the first NO-GO — a gate that keeps going after a failure gets ignored.
2 · Review the draft
Zoey shows the draft in chat. It has the same sections as a packaged skill — front matter, Shared
Runtime, Team, Config & CLI, Workflow, Output Economy — so every agent reads it the same way.
---
name: acme-predeploy-gate
description: "Pre-deploy gate before a production release — GO or NO-GO. Use when someone says deploy, go live, release to production, or asks whether main is safe to ship."
---
# Acme — Pre-deploy gate
Run before every production deploy. Read-only: it changes no workflow state and writes no code.
## Context
`php artisan larapilot:context deploy-gate` — with `--session={token}` when this conversation already holds one. Read every file under `data.runtime.read`.
## The Team
| Agent | Role |
| --- | --- |
| 🚀 **Jack** | Runs the gate, owns the verdict |
| 🔐 **Lars** | Security scan and its waivers |
| 🧪 **Anne** | Test evidence for what is about to ship |
## Config & CLI
1. `data.settings` comes from the `context` envelope.
2. This skill uses: `spec-list`, `doctor`, `quality`, `metrics`, `decision-log`, `notify`.
## Workflow
1. **Nothing half-done ships** — `php artisan larapilot:spec-list --status=REVIEW`, then
`--status="IN PROGRESS"`. Any spec listed → **NO-GO**; name it.
2. **Install healthy** — `php artisan larapilot:doctor`. `data.healthy` false → **NO-GO** with the failing checks.
3. **Quality green** — `php artisan larapilot:quality`. An `E_QUALITY` error → **NO-GO** with the findings.
4. **Security** — only when `data.settings.security_scan` is `YES`: `php artisan checkpoint:scan`.
A `FAIL` → **NO-GO**, unless the user grants a waiver; log it with
`php artisan larapilot:decision-log --topic="deploy waiver" --value="…" --source=chat --skill=acme-predeploy-gate`.
5. **Scope** — `php artisan larapilot:metrics` → one line: stories DONE and still open.
6. **Verdict** — `GO` or `NO-GO`, then the reasons. When `data.settings.notifications` is `YES`:
`php artisan larapilot:notify --event=custom --title="Pre-deploy: GO"`.
Stop at the first NO-GO. Never deploy, push, tag, or merge from this skill.
## Output Economy
**Low** — one line per check (`✓ doctor` / `✗ quality — 2 Larastan errors`), then the verdict.
3 · Save and register — one command
Sarah never writes the canonical file by hand. She saves the approved draft to a temp file and hands it
to Artisan:
That one call validates the skill (kebab-case name, non-empty description), writes the
canonical copy under .larapilot/skills/, mirrors the folder into .ai/skills/ —
Laravel Boost's custom-skill source — and into every agent skill folder that already exists
(.claude/skills/ here, because the project uses Claude Code), then runs
boost:update. The slash command is live; nobody has to run anything else. Sarah deletes the
temp file.
.larapilot/skills/
├── .gitkeep
└── acme-predeploy-gate/
└── SKILL.md # canonical copy — commit this
.ai/skills/acme-predeploy-gate/SKILL.md # Boost source (mirror)
.claude/skills/acme-predeploy-gate/SKILL.md # agent folder (mirror — only because it existed)
4 · Run it
Your editor — before the deploy
/acme-predeploy-gate
🚀 Jack: Pre-deploy gate — acme-shop.
first run
✓ backlog — nothing in REVIEW or IN PROGRESS
✓ doctor — healthy
✗ quality — Larastan: 2 errors in app/Policies/InvoicePolicy.php
NO-GO — fix the two Larastan errors, then run /acme-predeploy-gate again.
After the fix:
second run
✓ backlog · ✓ doctor · ✓ quality · ✓ checkpoint (0 FAIL, 2 WARN) · 14 stories DONE, 3 open
GO — Slack notified.
5 · Share it with the team
Commit .larapilot/skills/. On a teammate's machine, after git pull:
Teammate's terminal
php artisan larapilot:custom-skill-list # registers every skill found on disk
custom-skill-list — also run by larapilot:update and by opening the dashboard
Skills page — mirrors each skill into .ai/skills/ and the agent folders that
exist. It does not call boost:update; run that once if your editor's skill folder was just
created. /larapilot/skills lists the skill with its trigger, its description, the file path,
and a Registered chip.
6 · Change it, grow it, retire it
You want to
Do
Change the steps
Run /larapilot-custom-skill again and ask to overwrite, or edit a copy and re-save
it with larapilot:custom-skill-add --name=acme-predeploy-gate --file=… --force.
Without --force the command refuses to overwrite.
Add related skills
Round 1 → Several related skills: one custom-skill-add per skill under a
shared prefix — acme-predeploy-gate, acme-hotfix-gate,
acme-release-notes.
Remove it
Delete .larapilot/skills/acme-predeploy-gate/and its mirrors in
.ai/skills/ and the agent folders, then run php artisan boost:update.
Registration only adds or refreshes copies; it never deletes one.
You do
Describe the ritual, answer three rounds, approve the draft
Agent does
Drafts SKILL.md in the packaged-skill shape, saves it with
custom-skill-add, registers it with Boost
Next step
Commit .larapilot/skills/; run /acme-predeploy-gate before every
deploy
Deep Dive
Reference
Every skill, setting, file, and command, one chapter each. Read Concepts
first if a word is new, and use the search field at the top of the page to jump to a command or a
setting by name.
How it works
Three layers: skills orchestrate the conversation, artifacts in git are the source of truth,
and Artisan CLI persists state under the hood.
1 · Skills
/larapilot-* prompts published via Laravel Boost. You invoke them in the editor; the agent
reads SKILL.md and follows the execution contract.
2 · Artifacts
PRD, backlog, specs, plans, and review notes live under .larapilot/ in git — durable state
the agent reloads on every skill activation.
3 · CLI (under the hood)
Skills call php artisan larapilot:* to persist files and enforce status transitions. You
rarely run these directly — see Artisan CLI. After a package upgrade, run
Composer + larapilot:update to refresh shared runtime and
Boost skills.
What the agent loads — and what it skips
A skill opens with one command, php artisan larapilot:context {skill}. One envelope answers
the settings, the paths the skill uses, what the PRD says about the project, and the runtime files to
read. Three things keep the context small, and keep an agent from applying a rule that is not the
project's:
Rule
How
Only what the skill needs
Every skill has its own list of packs. runtime.read is what it reads before it
starts; runtime.on_demand names the heavy ones — tenancy, CI/CD, integrations, UX,
deploy platforms — with the moment that calls for them. /larapilot-triage reads the
core only: the branch not taken costs nothing
Only what the settings call for
The packs are compiled for the project into .larapilot/cache/runtime/. Under
GITFLOW an agent reads the GITFLOW rules and never sees the other two
modes; a toggle that is off has no section at all. Change a setting and the next call hands out
the files that changed
Only once per conversation
The call returns a session token. Passed back with --session=, it tells the next
skill of the same conversation which files are already loaded (runtime.loaded): it
reads the new ones and nothing else. After the conversation is compacted,
--fresh reads everything again — a summary keeps the token and loses the rules
Loaded (skill + runtime, default settings)
v4
v5
/larapilot-implement, first skill of a conversation
~43k tokens
~15k
/larapilot-review after implement, same conversation
~38k
~2k
triage → bug → plan → implement → review, one conversation
~173k
~31k
Runtime files are read with the editor's file-read tool, never
cat/head/sed; each stays under 15 KB so one read returns it
whole, and a truncated preview counts as a failed load. Commands answer with the slice, too:
spec-list is the backlog without the bodies (--full for everything),
prd-show reads the PRD by the piece (its outline, --ids=FR-004,J-001,
--section="Technical Architecture"), and spec-show US-004 --task=TASK-02 one
task. Chat stays short; artifacts on disk stay complete.
.larapilot/shared-runtime.md and the runtime-*.md files stay in the repository
as the index for people, and as the fallback when the command cannot run.
.larapilot/cache/ is derived, ignores itself in git, and is rebuilt whenever it is stale.
Upgrade
Larapilot ships as a Composer package and follows semantic versioning. Every upgrade
makes the same two moves: bring the new package into vendor/, then run
larapilot:update to refresh the files Larapilot publishes into your project. Your
workflow data is never migrated — .larapilot/config.yaml, PRD, backlog, specs, plans, and
decisions are read as they are. What the release changes is how you bring it in.
Release
Example
Bring it in with
Read first
Patch or minor
4.1.2 → 4.1.4 5.0 → 5.1
composer update, inside the constraint your composer.json names
versions is what the project runs, latest what Packagist has: a different
first number is a major. composer why gives the constraint and where it sits —
requires is require, requires (for development) is
require-dev, where the install command puts it. A major upgrade needs to know
which.
Commit what changed — composer.lock, the runtime in .larapilot/, the skills
and guidelines Boost republished for your editors — then open a new conversation with your agent: the
one already open keeps the skills it loaded when it began.
Major releases, step by step
Shown for 4.x → 5.0; for a later major, change the numbers. Work on a branch, so the upgrade is one
commit you can read, test, and throw away.
Read what breaks. The Breaking section of the new version in the
changelog says which scripts and custom skills may need a change; step 6
finds them. For 5.0, the whole picture is Version 5 vs version 4.
Pick a quiet moment. No /larapilot-implement or
/larapilot-autopilot running: the package, the runtime, and the skills would change
under the agent halfway through a story. Specs in progress are fine — they are read as they are.
Branch from a clean tree, and rehearse.
Terminal — rehearsal, writes nothing
git switch -c upgrade/larapilot-5
git status --short # prints nothing: the upgrade will be the only change here
composer require andreapollastri/larapilot:^5.0 --dev --with-all-dependencies --dry-run
The dry run resolves the new version and lists every package it would move. It writes nothing —
not even composer.json, although it prints that it was updated.
--with-all-dependencies lets Composer move what Larapilot depends on, Laravel and
Boost included, within the constraints of your composer.json. When it cannot
resolve, its message names the package that holds the upgrade back.
composer why said requires, not requires (for
development)? Drop --dev, from the rehearsal too. With it, Composer moves
Larapilot to require-dev — and without asking when it runs non-interactively, as in
a script.
Edited the packaged design systems? Run
php artisan larapilot:update --preserve-design-systems: without the option,
the packaged copies overwrite .larapilot/design-systems/.
Read what larapilot:update printed. Most of it reports work done;
these lines ask something of you.
The line says
What to do
config.yaml is missing setting keys …
Nothing is broken: the defaults already apply. To keep a choice on record, set it with
/larapilot-settings or php artisan larapilot:settings-set.
Design systems refreshed …
Printed on every run without --preserve-design-systems. If you had edited them:
git restore .larapilot/design-systems, then
php artisan larapilot:update --preserve-design-systems --skip-boost.
Left in _project_docs …
Merge the files it names by hand, then delete _project_docs/;
larapilot:doctor warns until it is gone.
… date(s) do not hold and … finding(s) need a decision
Run /larapilot-schedule when you are ready to re-plan: the update changes
no priority, blocker, estimate, or date.
boost:update is not available / failed
Not available: Boost is missing —
composer require laravel/boost --dev. Then, either way,
php artisan boost:install once and php artisan larapilot:update
again.
Find what the major breaks — 4.x → 5.0.
Terminal — 4.x → 5.0 checks
# scripts and CI — the files Larapilot and Boost publish are left out
git grep -nE "larapilot:spec-list|larapilot-boogle|larapilot:vps-provision" -- \
':!.larapilot' ':!*SKILL.md' ':!CLAUDE.md' ':!AGENTS.md'
# your custom skills
git grep -nE "spec-list|larapilot-boogle|runtime-[a-z]+-[0-9]+\.md" -- .larapilot/skills
No output means nothing to change. A hit in another editor's guideline file
(.cursor/rules/, .github/copilot-instructions.md, …) is Larapilot's own
text, refreshed by the upgrade: skip it. For each other hit:
spec-list answers without the bodies now: add --full
where the script or skill reads body or status_history.
larapilot-boogle is /larapilot-error. The
boogle-* commands still answer under their old names.
larapilot:vps-provision is gone, with the
provision.sh it generated: drop the call. A server already set up with that script is
not touched by the upgrade — the prj-* tools it installed never called the package —
but nothing maintains them any more: deploy those projects with the platform their PRD records
(Forge, Ploi, Cipi, …) through /larapilot-ship.
runtime-…-N.md in a custom skill names a part of a pack that v5
re-split by audience: cite the heading instead, or start the skill with
php artisan larapilot:context {its name} --with=delivery-1,dev-docs
(Custom skills).
An external frontend is linked? Run /larapilot-frontend-companion
once: in a monorepo it asks which projects are this product's, and it reads the team's agent
rules from then on.
Commit, and open a new conversation.
Terminal — one commit for the upgrade
git add -A
git commit -m "Upgrade Larapilot to 5.0"
The commit holds composer.json, composer.lock, the runtime in
.larapilot/, and the skills and guidelines for your editors;
.larapilot/cache/ ignores itself. Open a new conversation with your agent — the one
already open keeps the 4.x skills it loaded when it began. After the merge, teammates run
composer install, and the published files come with the pull; where the editors'
skill folders are kept out of git, each teammate also runs php artisan boost:update.
larapilot:update stays with whoever makes the upgrade commit: it also runs
composer update laravel/boost, which belongs in that commit.
Roll back
Terminal — back to 4.x
# not committed yet? stash the attempt: kept, not lost
git stash push --include-untracked -m "larapilot 5 attempt"
git switch main # or the branch you started from
composer install # 4.x back in vendor/
php artisan boost:update # the 4.x skills back in your editors
The switch brings back composer.json, composer.lock, and
.larapilot/ as they were. The upgrade branch stays until you delete it:
git branch -D upgrade/larapilot-5.
What each step does
Command
Updates
Leaves untouched
composer update … patch or minor
PHP packages in vendor/ — Larapilot (services, CLI, dashboard, design-system
packages) and the latest stable laravel/boost, inside your constraints
Your .larapilot/ project config and workflow artifacts
composer require …:^5.0 major
The constraint in composer.json, then the same packages, plus whatever Larapilot
depends on — Laravel included — as far as your other constraints allow
Your .larapilot/ project config and workflow artifacts
larapilot:update every release
.larapilot/shared-runtime.md and every runtime-*.md pack (and removes
the ones the version no longer ships), task-templates.md, integrations.md,
packaged .larapilot/design-systems/ references (skip with
--preserve-design-systems); re-registers your
custom skills with Boost; moves a handbook an older version left
in _project_docs/ into .larapilot/docs/handbook/ (files the handbook
already holds stay behind, reported); the delivery forecast realigned for a
project that is already planned — an id and a plain date on a milestone that lacks them,
the release a milestone is named after, the deadline of an epic on its specs that lack it
— with one line on what the forecast now misses, for
/larapilot-schedule; missing phpstan.neon.dist /
pint.json and Composer quality entries; composer update laravel/boost
(latest stable) then boost:update to republish guidelines and
/larapilot-* skills
.larapilot/config.yaml (except an old paths.project_docs:
_project_docs/, repointed), PRD, backlog, specs, plans, mockups, domain docs, custom
skills, and other project-owned files
Options & checks
php artisan larapilot:update --skip-boost — refresh shared runtime and design-system copies
only (does not bump laravel/boost or republish skills); run
composer update laravel/boost --with-dependencies and
php artisan boost:update yourself when you want the latest Boost
php artisan larapilot:update --preserve-design-systems — keep
.larapilot/design-systems/ as it is, your edits included
php artisan larapilot:doctor --human — verify install health after an upgrade, as a
table (includes Larastan/Pint gate); without --human it answers JSON for scripts
php artisan boost:update --discover — one-time if Boost was installed before Larapilot and
skills are missing
Automate with Composer (optional)
Add a post-update-cmd hook so larapilot:update runs whenever Larapilot is updated
via Composer:
Keep --skip-boost in the hook so Composer does not recurse into
composer update laravel/boost. Omit it only when you want Boost guidelines and skills
republished on every Composer update (the package bump is skipped automatically inside a Composer script).
Run php artisan boost:update manually when you use --skip-boost in the hook, or
include laravel/boost in the Composer update command so the lockfile tracks latest. The
hook keeps patch and minor releases in step; a major still takes the steps
above.
Do not re-run larapilot:install on an existing project
unless you intend to scaffold from scratch (--force). Use larapilot:update for
routine upgrades and majors alike.
Skills
Twenty-nine skills cover the life of a product: discovery, delivery,
changes after launch, security and upgrades, ship, and the tools around them. Each one is a SKILL.md file that
names the personas who speak, the steps they follow, and the commands they may call. Laravel Boost
publishes them into your editor on php artisan boost:install.
Your own skills live in .larapilot/skills/ — see
Custom skills. A short Boost guideline ships
alongside: it tells the agent what Larapilot is and which skill to reach for.
When you type /larapilot-plan US-001, the agent:
Loads the skill instructions and runs larapilot:context plan: settings, paths, and the
runtime files this skill reads — minus the ones the conversation already loaded
Reads current artifacts (PRD, spec, backlog status)
Runs the guided conversation with the right personas
Persists output via Artisan commands defined in the skill — never inventing its own file writes
The skills below are grouped by when you reach for them: the core loop runs on every
story; the other groups plug in around it.
Core loop
Runs on every story: idea → PRD → backlog → plan → code → human gate.
discovery
/larapilot-inception
A guided product interview that ends with PRD.md. It is run as a
conversation, not a questionnaire: every answer gets a reaction before the next
question, and AskQuestion is used only for the fixed choices Larapilot stores, three at most per
round, all skippable. It goes through these steps, in order:
The goal is challenged — at least two exchanges before any requirement is
written: who has this problem and what they do instead, what changes if it works, how you will
know in 90 days, the riskiest assumption, what would make you stop
Four rounds always happen, legacy rewrites included: Project Kind (Personal ·
Website · Application · Package), Delivery Target, Business Model, Operations & support. A
skipped round is written as Not decided, never as a guess, with what skipping it
costs
Prior-art check (prior_art, on
by default) — Sebastian states his queries, asks your consent, searches for products and
packages that already do it, writes research/prior-art.md, and you record the
verdict: Build anyway · Adopt / fork ·
Integrate as dependency · Not checked
The product is written down — User Journeys, Domain Model, every requirement
with a named actor and verifiable Done means, quality targets with a number and
a verifier, Risks & Assumptions. Each section is explained in
The PRD
Architecture choices are asked, never assumed — frontend topology, admin
panel, data store, local development, deploy platform. Lucille asks for deadlines; on a new
project one question offers release mode
Ready check and readback — Tom runs a ten-point Definition of Ready, Mark
reads the decisions back in at most twelve lines, and the PRD is written once, after you
confirm
It reads client-materials/ and legacy/ first when they hold files. The
Package kind adds its own questions: origin, distribution, consumer install,
CI, docs. When a PRD already exists, inception does not overwrite it — it offers a revision
through /larapilot-prd, or a pivot.
Turns the PRD into a backlog of user stories: backlog.yaml plus one
specs/US-XXX.yaml each. On an empty backlog it builds all of it; on an existing one
it adds only what is missing.
One story per journey by default. The
backlog setting makes stories coarser or finer
How much is built follows the MoSCoW tags and the delivery target: an MVP
gets the Must requirements, a V1 adds the Should, a full product
adds the Could. Won't never gets a story
Acceptance criteria come from each requirement's Done means, the journey's
failure modes, and the quality targets that apply
A requirement blocked by an open question gets its story with the question written in it
— never a guessed answer
It ends with a coverage check: every Must requirement has a
story, or a stated reason why it waits
Technical plan with tasks, test strategy, Git deliverables → plans/US-XXX-plan.yaml.
Status → PLANNED. On a large codebase, an optional
read-only explore sub-agent maps the code first (never under effort: ECO). When
autopilot runs this step, that mapping stays inside the spec worker — no nested sub-agent.
CLI:larapilot:validate-plan ·
larapilot:spec-plan — validate then persist the plan.
build
/larapilot-implement US-XXX
Executes the plan: Laravel code, Pest tests, per-task commits, and the
developer domain doc for every domain the task touched (English,
in the same commit). Runs larapilot:quality
(Pint + Larastan level 5+) on backend tasks before task-done. After the tasks, Robert
and Lars review the diff in parallel (inline under ECO). Status → REVIEW when all tasks complete. Autopilot runs only Phase 1
in the worker; the parent session launches that review and calls spec-review.
Human gate. You approve → DONE, or send back with feedback →
TODO.
CLI:larapilot:spec-approve or
larapilot:spec-request-changes.
Onboarding — no PRD yet
Pick the one that matches where your project stands, then continue with the
core loop.
brownfield
/larapilot-adopt
Brings an existing, running Laravel app — built without Larapilot — under the
workflow. Reverse-engineers a complete PRD from the code itself: models &
migrations, routes/controllers, jobs, policies, config, packages, CLI/scheduler, CI, and frontend
surfaces. Personas + functional requirements are inferred from shipped behavior with file-path
evidence; the agent asks only the gaps the code can't answer.
Runs right after php artisan larapilot:install, when there is no PRD
Writes .larapilot/research/codebase-analysis.md + the PRD; records
Project Origin: Adopted (existing codebase) — scoped like Greenfield (no legacy
parity contract)
Optional read-only Explore sub-agent for large repos (never under
effort: ECO)
Not for greenfield ideas (/larapilot-inception) or rewrites away from a non-Laravel
system (/larapilot-inception + .larapilot/legacy/)
When release_mode=YES: release-import rebuilds shipped releases from Git
tags; AskQuestion for current production version and in-progress release branches
CLI:larapilot:context ·
larapilot:prd-write · larapilot:validate-prd ·
larapilot:choices-set · larapilot:release-import ·
larapilot:release-add. See the adoption walkthrough and
/larapilot-release.
greenfield
/larapilot-inception(idea, or legacy rewrite)
Start here for a new product idea, or to rewrite / port away from a legacy
non-Laravel system — drop snapshots in .larapilot/legacy/ and Sabrine leads a parity
contract. Full card above under Core loop.
Shortcuts
Skip the full discovery loop for small, well-scoped work on a product that
already has a PRD.
one feature
/larapilot-feature "…"
Mini-inception for one enhancement on a shipped product → new US-XXX spec (+ optional PRD
FR). Criteria are built from the FR's done-means, the journey's failure modes, and the NFR targets
that apply; Tom runs a five-point ready check and Mark reads the feature back before it is
persisted. A change request edits the FR in place — same id, before → after in the
revision history — and prd-impact names the other stories resting on it. When release_mode=YES and open releases exist, AskQuestion assigns the spec to a
release (or backlog) → **Release:** x.y.z line + release-set --add-spec=.
CLI:larapilot:spec-add ·
larapilot:validate-spec · larapilot:release-list ·
larapilot:release-set --add-spec= — optionally prd-write when a new FR is
added.
bug triage
/larapilot-bug "…"
Sophia-led triage → fix spec or rework route. PRD updated only on requirement gaps. The agent
reads the logs of the application every time, whatever the report says —
larapilot:logs, the repeats counted and the
secrets redacted — and quotes the exception, its place, and how many times it was thrown.
Every fix quotes the promise broken — an FR done-means, an acceptance criterion,
or an NFR target — so "fixed" has a definition. The route follows the status of the story:
spec-request-changes in REVIEW only, a re-issue through spec-add for TODO
and PLANNED, a new fix spec for DONE. A defect already in the intake log gains an occurrence, not a
second story.
CLI:larapilot:spec-add or
larapilot:spec-request-changes — plus larapilot:logs every time,
larapilot:diagnostics for the health of the runtime, and
larapilot:prd-impact on a requirement gap.
bug or feature?
/larapilot-triage "…"
Front door for a request you cannot place. Applies the promise test: a bug when an
FR or an acceptance criterion promised the behavior (or it worked before), a feature when nothing
did — a change to what was agreed included. States the verdict with its evidence in one line, then
runs /larapilot-bug or /larapilot-feature in the same turn, with the
answers it already has. Asks one question only when the evidence does not settle it. Writes no spec
itself. Both target skills run the same check on their own evidence and hand over to each other
once when the label was wrong. Walkthrough.
CLI:larapilot:spec-list ·
larapilot:spec-show US-XXX --fields=id — larapilot:decision-log when you
settle the verdict.
PRD revision
/larapilot-prd "…"
Changes the PRD when the change is neither a bug nor a feature. Mark names the
revision kind — editorial, sharpen, re-scope, re-model, re-decide, upgrade — the
team interviews in proportion to it, Tom traces the touched ids to the stories that cite them, and
Mark reads the delta back before anything is written. Ids are never renumbered or reused; a dropped
requirement is retired with its heading kept. Also reconciles a PRD you edited by hand and brings an
older PRD up to the current shape. Walkthrough.
CLI:larapilot:validate-prd ·
larapilot:prd-impact (--ids=) · larapilot:prd-write ·
larapilot:choices-set --from-prd · larapilot:spec-add /
spec-request-changes / spec-delete to align the stories.
Optional steps
Insert into the loop when the work calls for it — none are required to ship.
mockups
/larapilot-design US-XXX— before plan
Static HTML mockups in mockups/{spec}/, styled from packaged references — Filament,
Starter Kit, Bootstrap 5, Tailwind, or AdminLTE per PRD. Preview at /mockups/{spec}
(non-production), on the spec page, and in the dashboardDesign gallery (/larapilot/design) — every screen as a card you
click to browse the mockup like a site, zip download of HTML + assets. A mockup
never contains a field for a username or a password:
it draws them.
CLI:larapilot:context — resolve
paths.mockups and paths.design_systems.
batch delivery
/larapilot-autopilot US-XXX …— many stories at once
Chains plan + implement for multiple specs, one at a time, on the same
working tree. Under STANDARD or MAX each spec gets two writing sub-agents
in sequence — one for the plan, one for implement Phase 1 — and then this session takes over:
spec-plan, spec-start, task-done, the Robert/Lars review, and
spec-review. The worker cannot ask you a question and cannot change workflow state; a
missing decision comes back as BLOCKED and this session asks. This session does not
load the delivery packs, domain-doc rules, or the PRD; the delivery target is
choices.yaml. Robert and Lars return at most 8 bullets, and the diff stays in the
review file. ECO, and an
editor with no sub-agent tool, run plan and implement inline. By default you still run
/larapilot-review per story; with auto_approve: YES it may
spec-approve after implement.
CLI: same as plan/implement; plus
larapilot:spec-approve when auto-approve is on.
ship
/larapilot-ship— when the MVP is DONE
OWASP security gate + multi-platform deploy runbook when target stories are DONE. With release_mode=YES, Sarah runs the
release ship ceremony (merge release/x.y.z → main, tag
vX.Y.Z, back-merge → develop) before or alongside deploy.
CLI:larapilot:context ·
larapilot:metrics · larapilot:release-list --status=in_progress when release
mode is on.
Project setup & ops
Configure how Larapilot works on this project, and follow effort over time.
config
/larapilot-settings
AskQuestion for persistent project settings, written to .larapilot/config.yaml:
Process — effort, backlog granularity,
git_mode, testing, auto_approve, lucille
Traceability — decision_log (journal + regression guard, ON by
default), code_history (per spec/task file+line log, OFF)
Discovery — prior_art (Sebastian's existing-solutions search at
inception, consent asked before every search, ON)
Business — account (NONE · FREELANCE ·
COMPANY; anything but NONE unlocks Economics)
Access & security — comments (dashboard + API internal
feedback), dashboard_auth (HTTP Basic Auth on the dashboard UI),
api_auth (mandatory LARAPILOT_API_TOKEN on the JSON API),
security_scan (checkpoint in review and pre-ship) — all OFF by default
Manage the semver release ledger (.larapilot/releases.yaml): roadmaps, register releases,
assign specs, switch active release/* branches, import history from Git tags, move status
toward ship. Sarah owns Git mechanics; Jack owns policy; Mark owns scope.
Wired into /larapilot-inception, /larapilot-adopt,
/larapilot-feature, /larapilot-plan, and /larapilot-ship.
With Gitflow: specs assigned to a release branch from release/x.y.z (TASK-00
variant in task-templates.md).
Parallel releases — Sarah confirms the active release branch when several are
open.
Status
Meaning
Next step
planned
Registered, scope negotiable
Assign specs → in_progress
in_progress
Actively built on release/x.y.z
Ship ceremony → shipped
shipped
On main, tagged vX.Y.Z
Terminal — new row for follow-up work
Ship ceremony (Gitflow + release mode): php artisan larapilot:release-ship --semver=x.y.z
merges release/x.y.z into main, tags vX.Y.Z, back-merges into
develop, and marks the ledger shipped. Feature work on that release is
larapilot:release-feature --spec=US-XXX. release-list reports
git.needs_choice so a branch is picked only when more than one release is in progress.
Use --semver=, not
--version= (Artisan reserves --version). Add --push only when
pushing is intended. Runtime pack:
.larapilot/runtime-release.md.
Albert maintains a living technical + functional handbook under
.larapilot/docs/handbook/ — chapters,
Mermaid diagrams, API/CLI/architecture coverage. Bootstrap retroactively when enabled mid-project;
incremental updates after implement/review/ship.
CLI:larapilot:context ·
larapilot:spec-list — no dedicated write command; the skill updates Markdown chapters
directly.
Full reference
Enable with settings.project_docs: YES (larapilot:settings-set
--project-docs=YES). Default path: .larapilot/docs/handbook/, seeded on install with a
README.md that the bootstrap replaces with the index. A handbook an older version wrote in
_project_docs/ at the repo root is moved there by larapilot:update.
.larapilot/docs/handbook/
├── README.md # index (a stub until the bootstrap)
├── 01-overview.md
├── 02-architecture.md
├── 03-features/
├── 04-api.md
├── 05-cli-and-jobs.md
├── 06-frontend.md
├── 07-operations.md
├── 08-releases.md # when release_mode=YES
└── diagrams/
After material changes: implement → feature chapter; review/ship → operations/releases. Never store
secrets or user-specific paths — env var names only. Mid-project bootstrap: Albert
scaffolds from PRD, specs, plans, and git history; marks uncertain blocks
<!-- TODO: verify -->. Runtime pack:
.larapilot/runtime-project-docs.md.
extend
/larapilot-custom-skill
Turns a team ritual into your own slash command — a deploy gate, a compliance check, client
release notes, an internal runbook. Zoey interviews you in three rounds (one skill
or a family · slash name and trigger description · personas, runtime packs, and CLI calls),
Albert tightens the steps, and Sarah saves the result with
larapilot:custom-skill-add. The skill lands in
.larapilot/skills/{name}/SKILL.md, is registered with Boost in the same call, and shows
up on the dashboard Skills page.
Aurora calibrates country + tax regime (forfettario, SRL, Ltd, …) and produces a
quote, net-to-owner after FY-2026 rates, payback, and — for SaaS — ARR, break-even customers,
hosting, and a 36-month forecast. Requires settings.accountFREELANCE or
COMPANY. Hours come from the backlog spec by spec (planned task hours, then story
points, with hours-per-point calibrated on the plans you already wrote), and the snapshot
refreshes itself whenever specs, plans, the PRD, or inception change.
/larapilot/economics is an interactive pricing tool: dropdowns for
hourly rate, margin, commercial discount, team size, account type,
country, regime, monthly price, BASE / PRO / PREMIUM price line and market scenario
recompute every figure, chart and the quote download live — server side, through the same snapshot the
CLI uses. Nothing is written from the browser: a simulation prints the economics-set
command that would make it real. A discount comes out of the margin and flags the project when it drops
below cost; team size compresses the timeline and never the price.
Sold as decides which half of the page is the real one, without ever moving the
hours or the build price: fixed (one shot — the client pays once, payback is read as
capacity), saas (subscription — packaging, break-even customers, a 36-month business
plan), ecommerce (one-shot price, payback read in orders per month), and
package (one-shot price, payback read in licences). Left on auto it follows
the Business Model answer from inception. Each one is explained in place on the page, and in full
under Sold as.
For a subscription product the page carries three price lines with the slice of the backlog each one
includes, and a business plan read three ways — pessimistic, realistic, optimistic,
36 months each. Jennifer and Benjamin research the market during the
skill (sector, named competitors with their price and trend, demand, packaging) and persist it with
larapilot:economics-market-write; the dashboard plots it and shows where your price sits
against the researched set. Larapilot never invents a competitor or a price. On a plain one-off client
delivery that research step is skipped unless you ask for it — the page says so
instead of pointing at a command that would do nothing.
The page itself reads in the PRD's language
(en · it · es · fr · de · pt · nl · pl, English otherwise), like the client
quote — headings, captions, warnings and glossary. The pricing console stays in English: its labels
and the economics-set command it prints are operator controls.
Aurora also writes the client quote — a commercial document in the PRD's own
language, any language — to .larapilot/docs/quote.md, served at
/larapilot/economics/quote.md and by the dashboard's
Download quote button. A built-in en · it · es · fr · de · pt · nl · pl
template covers projects where nobody wrote one yet. Internal tax report:
/larapilot/economics/report.md.
Lucille interrogates the committed time/token ledger and schedule — overview,
filters by category/user/skill/spec/date, deadline drift, and Markdown report export.
Read-only while settings.lucille is NO. Full reference:
Usage & Lucille.
Lucille puts a project that is already planned back in line with its delivery
forecast. She reads the queue of the open specs and what the forecast could not read in its
inputs — a blocker that is not in the backlog, a spec with no points, tasks with no
estimate, a milestone that says on track and is not — then settles with you, date by
date, what to do with each one the forecast misses: move it, reorder, defer scope, or flag it.
Every change is forecast with a dry run and shown before → after; nothing is
written until you say so. Run it once on a project planned before v5.0.0, when every spec
started on the first day.
larapilot:schedule-show — the queue in delivery order with its dates,
epics, milestones and releases against the forecast, alerts, findings
larapilot:schedule-apply --file=replan.json --dry-run — priorities, points,
blockers, task hours and assignees, epic deadlines, milestones, in one batch
larapilot:schedule-apply --repair — what takes no decision; run by
larapilot:update
Connect Larapilot to the tools your team already uses. All OFF until
configured — see Forges & notify.
frontend repo
/larapilot-frontend-companion
For API + external frontend: link the FE absolute path (stored in
LARAPILOT_FRONTEND_REPO_PATH in .env, never committed in YAML), name the
projects of this product when it is a monorepo (Nx, Angular CLI, pnpm / yarn workspaces, Turborepo),
and scan: workspace, the team's agent rules (AGENTS.md, CLAUDE.md, Cursor,
Copilot, …), the conventions measured on the code, the commands, the API client. All delivery stays
on Laravel via repo: frontend tasks — or goes to the frontend team as a brief.
External frontend repo.
Publishes the repo into a Backstage developer portal — catalog entity
(catalog-info.yaml) plus TechDocs generated from PRD and backlog. Asks for owner,
system, and lifecycle, then generates. Full reference:
Backstage portal.
CLI:larapilot:backstage-export (preview) ·
--write to generate.
tracker sync
/larapilot-tracker
Mirrors the backlog into Linear, Asana, Jira,
Trello, ClickUp, or Monday over an API key.
Stories become issues, plan tasks become native subtasks, and remote status comes back as a drift
report. Full reference: Project trackers.
Downloads the open findings Aikido has for the repository, shows the ones
nobody decided about, and hands each one you pick to
/larapilot-triage, which routes it to /larapilot-bug or
/larapilot-feature. It runs no scanner and writes no spec itself. Needs
aikido=YES. Full reference: Aikido.
Asks which tracker records the errors of production when none is set — Boogle, Sentry,
Bugsnag, Flare, Datadog, Rollbar, Honeybadger, or CloudWatch — and turns the errors on.
Then it downloads the open ones, has you confirm each bug,
groups the confirmed codes with larapilot:errors-plan, and hands
each group to /larapilot-triage, which routes it to
/larapilot-bug or /larapilot-feature. It writes no spec itself,
and it never reads who the user of a request was. Full
reference: Production errors.
Move the stack forward and keep the dependencies clean. Each upgrade skill
starts with a readiness report and changes nothing until you choose — see
Laravel, PHP & DB upgrades and
SBOM, CVE & Checkpoint.
laravel
/larapilot-laravel-upgrade
From the Laravel the project is on to the one you name. Andrew runs the readiness
check and the Composer dry run, shows the criticalities, then — when you say upgrade now —
goes one major at a time on its own branch: the Composer change, the upgrade guide of
that version (read through Boost Search Docs), Filament's upgrade script,
livewire:upgrade, Inertia server and adapter together, Nova's guide, the gates, one
commit per step. Ends with an upgrade report, deploy runbook, and rollback.
The suite on the target PHP first, then the packages that exclude it, the code it deprecates
(Rector, PHPStan on the target), every file that pins PHP — Docker, CI, Vapor, Herd, Sail,
config.platform.php — and the runbook for Forge, Cloud, Vapor, or a VPS.
MySQL 5.7 → 8.0 → 8.4, MySQL → MariaDB or PostgreSQL, a PostgreSQL major, SQLite → a server.
Mike makes raw SQL, migrations, and configuration portable, proves it on a local
rehearsal against a scratch database, and writes the data move and the cutover. It never touches a
production or shared database.
Every package of the SBOM — Composer, the JavaScript of the repository, the frontend companion —
against OSV.dev. You decide package by package: update now (its fix command,
the suite, one commit), backlog (to /larapilot-triage), or waive with a
reason. The result is on the SBOM page.
The twenty-nine packaged skills are the base layer. On top of them, every project can keep its
own Boost skills in .larapilot/skills/ — committed with the code, registered
with Boost automatically, and built on the same CLI the packaged skills use. They extend the loop; they
never replace it. Walkthrough: Your own skill.
Good candidates
Gates — pre-deploy GO/NO-GO, a hotfix checklist, a "ready for client demo" check
Compliance — GDPR data-map refresh, an accessibility pass, a licence audit of new
dependencies
Communication — client release notes from the stories DONE since the last tag, a
weekly status digest from metrics and usage-report
House rules — how your team scaffolds a Filament resource, names queues, or wires a
webhook
If it changes workflow state (a spec's status, the backlog, the PRD), it belongs in a packaged skill or
its CLI — a custom skill calls those commands, it does not reinvent them.
Anatomy of a SKILL.md
.larapilot/skills/{name}/SKILL.md
---
name: acme-release-notes # kebab-case, 1–63 chars — also the slash command
description: "Client release notes from the stories DONE since the last tag. Use when someone asks
what shipped, for a changelog, or for notes to send the client." # required — Boost routes on it
---
# Acme — Release notes # optional title, shown on the dashboard Skills page
One paragraph on what it does — shown on the Skills page when there is no description.
## Context # larapilot:context {name} — settings, paths, files to read
## The Team # personas who speak, from the 30-persona roster
## CLI # the larapilot:* commands the skill calls
## Workflow # numbered steps, each naming its larapilot:* command
## Output Economy # how much goes in chat vs on disk
Three ways to save one
Terminal
# 1. Guided — Zoey interviews, Sarah saves (recommended)
/larapilot-custom-skill
# 2. From a file you wrote — name taken from --name, or from the front matter
php artisan larapilot:custom-skill-add --file=path/to/SKILL.md
php artisan larapilot:custom-skill-add --name=acme-release-notes --file=path/to/SKILL.md --force
# 3. Piped from stdin (or inline with --content=)
cat SKILL.md | php artisan larapilot:custom-skill-add --name=acme-release-notes
What custom-skill-add checks and does
Step
Behavior
Name
--name wins over the front matter and is written back into it. Must match
^[a-z0-9][a-z0-9-]{0,62}$, otherwise E_INVALID_INPUT (exit 2)
Front matter
Front matter with an empty or missing description is refused. A file with no front
matter at all gets one, with a placeholder description (Custom Larapilot skill.) —
replace it, since Boost routes on that line
Overwrite
An existing skill with the same name is refused unless you pass --force
Save
Atomic write to .larapilot/skills/{name}/SKILL.md (path key
paths.custom_skills)
Register
Mirrors the whole folder into .ai/skills/{name}/ — always — and into each agent
skill folder that already exists: .claude/skills/,
.cursor/skills/, .github/skills/, .agents/skills/,
.codex/skills/, .gemini/skills/, .junie/skills/,
.opencode/skills/, .windsurf/skills/
Publish
Runs boost:update when Boost is installed; the envelope reports
data.boost_published
A skill folder can carry more than SKILL.md — a template, a checklist, a script. Everything
in the folder is mirrored.
Lifecycle
Moment
What happens
larapilot:install
Creates .larapilot/skills/ with a .gitkeep, so the empty folder is
committed
Re-register every skill found on disk (copy into .ai/skills/ and existing agent
folders). This is how a teammate picks up skills after git pull
Edit
Re-save with custom-skill-add --force, or re-run
/larapilot-custom-skill
Remove
Delete .larapilot/skills/{name}/ and its mirrors in .ai/skills/ and the
agent folders, then php artisan boost:update. Registration adds and refreshes copies;
it never deletes one
Quality bar
Start with php artisan larapilot:context {name} (add --with=delivery-1,dev-docs
for the packs it needs), read what it lists, and honor data.settings (effort, git mode,
security scan, notifications…) like a packaged skill.
Persist only through larapilot:* commands. New files go under existing
.larapilot/ paths — never ad-hoc YAML beside the workflow artifacts.
Reuse persona names from the roster
(config-show --only=personas).
No machine-specific absolute paths — use env vars, as in
Environment paths.
Prefix the name with your team or client (acme-) so it never collides with a packaged
larapilot-* skill.
Runtime pack: .larapilot/runtime-custom-skills.md. There is no setting to turn custom skills
on — once saved, a skill is available through its slash command.
Workflow hooks opt-in
A story always moves through the same transitions — planned, started, task done, review,
approved or sent back, released, shipped. Hooks attach your team's own commands and skills to those
moments. They live in .larapilot/hooks.yaml, committed so the team shares them, and run only
while settings.hooks is YES.
.larapilot/hooks.yaml
hooks:
task.done:
before:
- name: Tests
run: php artisan test --compact
timeout: 900
spec.review:
before:
- name: Static analysis
run: vendor/bin/phpstan analyse --no-progress
- skill: acme-a11y-check
spec.approved:
after:
- name: Deploy to staging
run: curl -fsS -X POST "$FORGE_STAGING_DEPLOY_URL"
ship:
before:
- skill: acme-predeploy-gate
after:
- name: Deploy to production
run: php vendor/bin/envoy run deploy
Events
Event
Fired by
Moment
prd.written
prd-write
The PRD is saved
spec.added
spec-add
Stories enter the backlog
spec.planned
spec-plan
A plan is saved: PLANNED
spec.started
spec-start
PLANNED → IN PROGRESS
task.done
task-done
One task of the plan is done
spec.review
spec-review
IN PROGRESS → REVIEW
spec.approved
spec-approve
REVIEW → DONE
spec.changes_requested
spec-request-changes
REVIEW → TODO, with rework
release.shipped
release-ship
The release is merged and tagged (release_mode)
ship
/larapilot-ship, through hook-run ship
before as the gate starts, after on a GO
Phases and kinds
before runs after the command's own checks and before anything is
written. A hook that fails — an exit code other than 0, or past its timeout — refuses the transition with
E_PRECONDITION and details.hooks, and nothing is written. blocking:
false turns it into a warning.
after runs once the state is written. A failure is reported under
data.hooks.after.warnings; the transition stands.
run: — Larapilot runs the command from the project root, with
LARAPILOT_HOOK_EVENT, _PHASE, _SPEC, _SPEC_TITLE,
_TASK, _STATUS_FROM, _STATUS_TO, _COMMIT,
_RELEASE in its environment and the event as JSON on stdin. The answer carries the last 40
lines of its output; the whole of it is in .larapilot/cache/hooks/.
skill: — the agent runs that skill, often one of your
custom skills. Before a transition, the command refuses until the
agent reports it run with --skill-hooks-done=name; after one, the answer lists it under
data.hooks.after.skills.
What to hook
Gates an agent cannot skip — the test suite, Larastan, npm run build,
composer audit, a coverage floor, before task.done or spec.review.
An instruction in a prompt can be forgotten; a hook runs inside the command.
Deploys — staging after spec.approved, production after a GO from
/larapilot-ship or after release.shipped.
Your rituals at the right moment — an architecture review after
spec.planned, an accessibility pass before spec.review, client release notes
after release.shipped.
What Larapilot does not integrate — Teams, Mattermost, or email; a time tracker on
spec.started; a wiki export after prd.written; an n8n, Zapier, or Make
webhook.
Nothing runs until you say so. Install writes hooks.yaml with every event
listed and every example commented out, and hooks is NO.
LARAPILOT_HOOKS_ENABLED=false in .env runs none on one machine.
A file with errors is a closed gate. While hooks are on, every transition is refused
until hook-list is clean or hooks are off; doctor reports
checks.hooks.
Artisan only. Hooks never run from the dashboard, the API, or MCP, where
hook-list is read-only. /larapilot/settings lists the hooks of the
project.
The hooks are yours. Agents never edit a hook or turn hooks off to get past one unless
you ask, and --force on spec-review / spec-approve never skips a
hook.
Secrets stay in .env. A hook reads them from its environment. Timeout 300
s by default (LARAPILOT_HOOKS_TIMEOUT), at most 3600 per hook.
Runtime pack: .larapilot/runtime-hooks.md, read by the skills that move a spec, write the PRD,
or ship, while hooks is YES.
Project settings
Persistent project modes — how much effort the agent spends, how the backlog is cut, how
Git and tests behave, and which opt-in extras are on. Configure with /larapilot-settings
(AskQuestion) or larapilot:settings-set. Values live under settings: in
.larapilot/config.yaml, which is committed, so the whole team works in the same mode. Every
skill reads them first via larapilot:context.
Set one or several at once: php artisan larapilot:settings-set --effort=MAX --testing=BEST.
Read them back with php artisan larapilot:config-show --only=settings. Yes/no settings are
stored as booleans in the YAML and reported as YES / NO. A setting that is
missing from the file takes its default. Every flag is listed
at the end of this chapter.
Effort
Value
Behavior
ECO
Token economy — never spawn sub-agents, including the autopilot spec worker
(plan and implement stay in the parent session); disables Lucille
automatically (re-enable via settings-set --lucille=YES without leaving ECO);
defer docs theater (README/PDF/diagrams) but still update OpenAPI when APIs change
and still write the developer domain docs;
skip deep reviews and E2E planning
STANDARD
Normal Larapilot behavior (default). Autopilot delegates each spec to a writing
worker, one at a time; explore during plan and the Robert/Lars review stay available
MAX
Deep mode on every flow — fuller persona rounds, always run explore/review
sub-agents when available, the autopilot spec worker on every batched spec, richer plans and
residual-risk notes
Backlog granularity
Value
Behavior
LEAN
Fewest specs — one per end-to-end user journey, related FRs merged (each cited as
Traces to: FR-XXX); technical seams, admin entities, and i18n locales are always plan
tasks; ≤ 5 epics per product
STANDARD
One spec per demonstrable user capability (default) — closely related FRs may
share a spec; Laravel seams (models, controllers, policies, UI, API resources) become plan tasks;
reuse existing epics
GRANULAR
Fine-grained — one spec per FR allowed; seam / Filament per-entity / i18n per-locale splits
allowed; multi-epic backlog expected. For large teams or spec-per-PR workflows
Granularity changes spec cardinality only — never coverage: merged scope stays traceable via
FR-XXX citations in spec bodies and plan tasks. Epics are always consolidated: reuse existing
EP-XXX before proposing a new one.
Git mode
Value
Behavior
NO_GITFLOW
No Gitflow ceremony — work on the current branch; no mandatory feature branch / internal PR
Full Gitflow with push and open/update of the internal PR toward
develop after each task
Push is never implied by GITFLOW alone. See also Git
workflow.
Testing
Value
Behavior
MINIMAL
Critical-path Pest/PHPUnit only — no Playwright, Dusk, browser E2E, or viewport matrix
NORMAL
Standard feature/unit/policy/API tests (default) — still no
Playwright/Dusk/E2E
BEST
Full bar — integrations, primary-journey E2E, Playwright or Dusk / Pest browser, viewport matrix
(375 / 768 / 1280), axe, Lighthouse when applicable
Auto-approve
Value
Behavior
NO
Human gate required (default) — specs stop at REVIEW; only you Approve via
/larapilot-review
YES
After implement reaches REVIEW,
/larapilot-autopilot may present a short checklist and call
spec-approve without waiting — opt-out of the usual human-in-the-loop DONE gate
Lucille
settings.lucille controls the silent usage ledger and schedule interviews. Missing key =
ON. Exclusion is opt-out only.
Value
Behavior
YES
Default — log tokens/time at skill end, ask deadlines at inception, surface
schedule drift, honor /larapilot-usage
NO
Excluded — no usage-log, no Lucille interview rounds;
/larapilot-usage may still read historical ledger. Set automatically when switching
effort to ECO (unless you pass --lucille=YES in the same
call)
Re-enable anytime with php artisan larapilot:settings-set --lucille=YES without leaving ECO.
Details: Usage & Lucille.
Decision journal & regression guard on by default
settings.decision_log (default YES) keeps an append-only, timestamped log of every
explicit user choice — fixed-choice answers and free-text directives like
“the background must be orange” — in .larapilot/decisions.yaml.
After a choice is settled a skill runs
php artisan larapilot:decision-log --topic="…" --value="…" --source=askquestion|chat --skill=….
Before recording a value for a topic that already has a decision it runs
php artisan larapilot:decision-check --topic="…" --value="…". When today’s value differs from an
earlier non-superseded one the check returns that decision (with its date) so the skill can ask you to
confirm — “on 2026-05-01 you chose orange; confirm red supersedes
it” — then re-logs with --supersedes=<id>.
The file is never rewritten in place; a reversal is a new entry. Read-only
larapilot:decision-check is also on the MCP allowlist.
Turn it off with php artisan larapilot:settings-set --decision-log=NO.
The dashboard renders the journal on the PRD page
(project / discovery vs per-story groups, timeline) and on each spec detail page (entries
for that story only).
Prior-art check on by default
settings.prior_art (default YES) adds one step to
/larapilot-inception, after the goal is challenged and before scope is written: is there
already a product, an open-source project, or a package that does this?
Consent first. A search query can expose a confidential idea. Sebastian shows the
queries he would run and asks: Search now · Search with generic terms only · Skip — I know the
alternatives · Skip — confidential.
Where he looks depends on the project kind: Packagist and GitHub for a package;
GitHub, self-hosted catalogs, and alternatives directories for an application; Laravel CMS and
commerce platforms for a site. A Personal project skips the step.
What you get — .larapilot/research/prior-art.md: up to five
candidates with license, stack, last release, what each covers and lacks, and what adopting it would
cost.
You decide: Build anyway, Adopt / fork,
Integrate as dependency, or Not checked. The verdict is written in the PRD
as **Prior Art:** and shown on the dashboard's Inception page.
When a mature product already does it and is not built on Laravel, inception says so and stops with
the report.
With no web access in the editor, or with the setting off, the PRD says
Not checked — never "nothing exists". Turn it off with
php artisan larapilot:settings-set --prior-art=NO.
Code change history opt-in
settings.code_history (default NO) records, per spec/task, which files and line
ranges were touched — read straight from the task’s git commit — into
.larapilot/code-history.yaml.
Enable with php artisan larapilot:settings-set --code-history=YES; then
larapilot-implement runs
php artisan larapilot:code-log --spec=US-XXX --task=TASK-NN --skill=larapilot-implement after
each task-done (--commit= / --range= override; falls back to the
working-tree diff).
php artisan larapilot:code-history --file=app/Models/Post.php (read-only, also on the MCP
allowlist) reports every touchpoint for a path.
Account opt-in
settings.account is NONE (default), FREELANCE, or
COMPANY. Anything but NONE unlocks /larapilot-economics and the
Economics page: FREELANCE prices with sole-trader regimes
(Italian forfettario, IRPEF, autónomo, …), COMPANY with corporate tax plus dividend
extraction (SRL, SPA, Ltd, GmbH, C-Corp, …). OFF is accepted as an alias of
NONE.
settings.release_mode (default NO) turns on semver release tracking in
.larapilot/releases.yaml, the /larapilot-release skill, release AskQuestion rounds
in inception/adopt/feature, Gitflow release/x.y.z branches when
git_mode is Gitflow, and the release ship ceremony in /larapilot-ship. Enable with
php artisan larapilot:settings-set --release-mode=YES. Skill:
/larapilot-release.
Project docs opt-in
settings.project_docs (default NO) obligates Albert to maintain the handbook in
.larapilot/docs/handbook/ — bootstrap + incremental chapter updates after material product changes. Enable
with php artisan larapilot:settings-set --project-docs=YES. Skill:
/larapilot-project-docs.
Workflow hooks opt-in
settings.hooks (default NO) runs the commands and skills of
.larapilot/hooks.yaml on the transitions of the loop: a before hook that fails
blocks the transition, an after hook is reported. Enable with
php artisan larapilot:settings-set --hooks=YES; LARAPILOT_HOOKS_ENABLED=false
turns them off on one machine. Details: Workflow hooks.
Internal feedback / comments opt-in
settings.comments (default NO) gates the comment form on dashboard spec pages,
POST /larapilot/api/specs/{code}/comments, and larapilot:spec-comment. Turn it on
with php artisan larapilot:settings-set --comments=YES. Comments land in
.larapilot/internal-feedback/{code}.md; a comment flagged [blocks-merge] stops
spec-approve until it is resolved (or --force). The env kill-switch
LARAPILOT_COMMENTS_ENABLED=false still disables writes even when the project setting is ON.
Projects installed before v3.0.3 with an explicit comments: true keep it.
Dashboard auth opt-in
settings.dashboard_auth (default NO) puts HTTP Basic Auth in front
of the dashboardUI and of the mockups at
/mockups. It never gates /larapilot/api/* (that stays
LARAPILOT_API_TOKEN) or the MCP server, and the dashboard is still never served in
production. On a shared staging host, turn it on before anything else: with the
setting OFF, the board, the PRD, the specs, the git history, and the security findings are open to whoever
reaches the host.
Credentials are argon2id/bcrypt hashes in .larapilot/auth.yaml — no
database, no User model, never cleartext. The file is added to .gitignore
automatically the first time a user is written.
Manage users with php artisan larapilot:dashboard-user {list|add <username>|remove
<username>} — add prompts for the password (or takes --password=).
Failed sign-ins are rate-limited per IP
(LARAPILOT_DASHBOARD_AUTH_MAX_ATTEMPTS, default 30/min). With the setting ON and
no users configured the dashboard fails closed with HTTP 503: every page
shows a notice that the area is protected by this setting and gives the command that creates the first
user. auth.yaml is git-ignored, so a fresh clone or a deploy starts on that notice.
The File manager, Database, and Logs pages go further: outside
local/development/testing they answer 404 until
this setting is YES.
Enable with php artisan larapilot:settings-set --dashboard-auth=YES. Serve over HTTPS on
shared hosts — Basic Auth sends credentials on every request. Setup:
Forges & notifications ·
.larapilot/integrations.md.
API auth opt-in
settings.api_auth (default NO) makes LARAPILOT_API_TOKENmandatory on every /larapilot/api/* request — the JSON API only.
It never gates the dashboard UI (dashboard_auth) or the MCP
server, and the API is still never served in production.
With api_auth=NO (default): the token is enforced on every endpoint when it is set;
when it is not, read endpoints stay open in the allowed environments and writes are refused outside
local/development/testing.
With api_auth=YES: every request — reads and writes, including
/diagnostics, /openapi.json, and /docs — must carry the token as
Authorization: Bearer <token> or X-Larapilot-Token: <token>. With the
setting ON and noLARAPILOT_API_TOKEN configured the API fails closed with
HTTP 503 and says why — a JSON message that names the setting and tells how to
add the token, or the same explanation as a page when a browser opens the API docs; a wrong or missing
token is HTTP 401.
The token lives in the caller's environment, never in .larapilot/. Enable with
php artisan larapilot:settings-set --api-auth=YES. Client setup + curl /
Laravel HTTP / fetch / CI examples: .larapilot/integrations.md →
API access.
The php artisan larapilot:diagnostics CLI and the MCP diagnostics tool are local
and need no token — only the HTTP endpoint is gated.
Security scan opt-in
settings.security_scan (default NO) folds a static Laravel security scan into
/larapilot-review and the pre-ship gate. It uses the optional dev package
andreapollastri/checkpoint
(php artisan checkpoint:scan — static checks plus composer/npm
audit), run through php artisan larapilot:checkpoint-scan so the result lands on
Security → Checkpoint. Larapilot never bundles the scanner; with the setting off it runs
only when someone asks — the command, or Run the scan on the dashboard. See
SBOM, CVE & Checkpoint.
FAIL findings are review blockers: fix them, or record a waiver with
larapilot:decision-log. WARN findings become review notes.
If the package is missing, the review skill stops and asks you to install it.
Owned by Lars, alongside dashboard_auth and api_auth. The client
quote only promises a security scan when this setting is YES. Setup:
.larapilot/integrations.md → Security scan.
Aikido opt-in
settings.aikido (default NO) reads the findings of
Aikido for the repository.
Aikido scans on its side; Larapilot runs no scanner and only reads the result, with credentials kept in
.env. While the setting is off, Aikido is never called.
Owned by Lars. Everything else — the credentials, the skill, the gate — is in
Aikido.
Production errors opt-in
settings.errors (default NO) reads the errors the running application
throws from one tracker, and settings.errors_provider names it:
boogle (the default), sentry, bugsnag, flare,
datadog, rollbar, honeybadger, or cloudwatch.
The credentials stay in .env. While errors is off no tracker is called,
and the tracker that was named is kept for when it is turned on again.
/larapilot-error asks which tracker when none is set, and turns the errors on.
settings.boogle is the name the setting had when Boogle was the only tracker:
--boogle=YES is --errors=YES --errors-provider=boogle, and the key is
kept in step with the two. A project that turned Boogle on before 4.1.3 has nothing to change.
Owned by Sophia. The trackers, what each needs in .env, and the
skill are in Production errors.
Remote forges opt-in
settings.github, gitlab, bitbucket, and azure (all default
NO) are orthogonal to git_mode. Enable the forge matching
origin:
Bitbucket Cloud — REST API with access token or app password; probe
larapilot:bitbucket-status
Azure DevOps — az repos (azure-devops extension) or REST API with a
PAT; probe larapilot:azure-status
When ON, skills open/update the PR/MR, always print the URL, and can notify
pr_opened / pr_updated. Full setup:
Forges & notifications ·
.larapilot/integrations.md.
Notifications opt-in
Master switch settings.notifications plus notify_slack /
notify_discord / notify_telegram (all default NO). Secrets stay in
.env. Fan-out via php artisan larapilot:notify. Hard hooks:
task-done → task_done, spec-approve → spec_done.
Full event list: Forges & notifications.
Environment paths
Machine-specific absolute paths must not appear in committed YAML or skill examples. Store
them in .env and resolve via config-show:
Concern
Env key
Set via
External frontend repo
LARAPILOT_FRONTEND_REPO_PATH
larapilot:frontend-set --path=… or /larapilot-frontend-companion
When a skill needs a path that is missing from env, it keeps asking until you provide it, then persists
with the matching CLI command. The PRD records the stack, not the absolute path.
30 personas are lenses, not costumes. Each skill activates a subset of the
squad — they speak in chat as 💎 Mark:, 📐 John:, etc., applying a specific kind
of scrutiny to the work at hand. Zoey (AI Guru) is active in every skill;
Lucille is active quietly by default (usage ledger). Nothing here is a separate agent with
its own memory: a persona is the point of view the agent takes while it does one part of the work.
Discovery
Zoey sharpens user intent and flags session-budget risks. Lucille
asks for delivery deadlines (skippable) and starts the ledger. Mark drives
scope and MoSCoW. Jennifer and Benjamin frame market context.
John and Aurora co-own architecture — John designs with
SOLID and N+1-aware query shape; Aurora brings SaaS economics.
Mike owns data architecture (SQL/NoSQL, tree patterns, search, migrations) when
persistence is non-trivial. Sabrine leads legacy parity when legacy/ has
content. Elise and Joe co-own the design system.
Emma, Lauren join for public surfaces; Ricky
for mobile apps; Albert scopes documentation; Sarah joins when
CLI/Git/CI/Linux surfaces appear.
Planning
Zoey recommends sub-agent vs inline passes. Tom sharpens acceptance
criteria. John designs the technical approach; Mike designs schema
and query plans. Anne defines test strategy.
Andrew enforces Laravel conventions.
Albert plans baseline docs on every spec and extended deliverables when approved.
Alex plans FE/BE integration with Andrew, Joe, and Jack. Sarah plans
CLI, Git mechanics (conflicts/rebase), forge automation, CI pipeline scripts, and Linux/server shell
tasks when those surfaces appear. Optional sub-agents explore the codebase in readonly mode.
Autopilot does that mapping inside the spec worker, so the parent session does not keep the files.
Implementation
Alex writes SOLID, N+1-free code;
Mike guides migrations and data access. Anne ships Pest tests and
documents manual tests. Robert checks plan adherence, Gitflow, and the quality bar —
involves Sabrine on refactoring/porting. Joe enforces design-system
consistency. Lars runs OWASP-aligned security review. Under autopilot those two
reviews start in the parent session, after the implement worker has returned. Sarah leads Git
conflict/rebase resolution and implements pipeline YAML, forge automation, and server shell scripts.
Ricky, Marika, Matt, and Albert join
when mobile, copy, integrations, or docs are in scope. Lucille logs the session
quietly at the end.
Review
Robert presents the human gate — including SOLID and
N+1 checks — with Sabrine on refactoring/porting specs.
Joe checks design-system compliance. Marika and
Emily verify typos and translation consistency. Anne attaches
automated evidence and manual test recommendations. Mike reviews schema/migration
risk when data tasks shipped.
Ship & support
Jack owns CI/CD gates and deploy orchestration; Sarah owns pipeline
scripts and server-side shell those runbooks invoke. Oliver red-teams before launch.
Albert ships client manuals and API docs. Sophia routes bugs and runs the front
door: with Mark and Tom she decides whether a request is a bug or a
feature (/larapilot-triage).
Violet and Emily cover compliance and localization.
Lucille reports schedule drift vs deadlines via /larapilot-usage.
Output economy varies by phase: concise status lines during implement/review; full artifacts and code stay
complete. See .larapilot/shared-runtime.md for brevity rules.
💎
Mark
Product Manager
🧭
Jennifer
Business Strategist
🏢
Benjamin
Business Consultant
💡
Sebastian
Innovator
🔎
Tom
Requirements Analyst
📐
John
Architect
🗄️
Mike
Database Expert
🔧
Alex
Full-Stack Developer
🧪
Anne
Test Architect
🛡️
Robert
Code Reviewer
🔐
Lars
Security Expert
🚀
Jack
DevOps Engineer
⌨️
Sarah
CLI, Git & Linux
📒
Lucille
Project Tracking
💰
Aurora
FinOps Expert
⚖️
Violet
Legal Expert
📈
Emma
SEO & Web Performance
💬
Lauren
Social Media Manager
🎨
Elise
UX Designer
✨
Joe
Frontend Expert
📱
Ricky
App Developer
📝
Albert
Tech Writer
🤖
Zoey
AI Guru
✍️
Marika
Copywriter
🔄
Sabrine
Legacy Porting & Migration
👾
Andrew
Laravel Expert
🔗
Matt
Integration Manager
🎯
Oliver
Ethical Hacker
🎧
Sophia
Support Manager
🌍
Emily
Translator
Workflow
Every user story moves through a strict state machine on a Gitflow branch
model. Skills drive transitions; invalid jumps are blocked automatically. After inception and backlog
creation, you repeat the per-story loop until the MVP is shipped.
Optional: /larapilot-design before plan ·
/larapilot-ship when all MVP stories are DONE ·
/larapilot-autopilot to batch plan+implement, one fresh context per spec · /larapilot-settings ·
/larapilot-usage · /larapilot-feature or /larapilot-bug on
existing products (/larapilot-triage when you cannot tell which) · Package kind skips UI mockups unless the package ships UI.
Git workflow — Gitflow
Git behavior is gated by settings.git_mode (set via /larapilot-settings). Default is GITFLOW
without automatic push. Choose GITFLOW_PUSH for push-after-each-task, or
NO_GITFLOW to skip branch/PR ceremony. Jack proposes the workflow at
inception; Sarah owns Git mechanics (conflicts, rebase), forge automation, and CI pipeline
scripts; Robert enforces the active mode.
When two developers work the same project and a merge conflict lands — in source or in a
.larapilot/ artifact like backlog.yaml — resolve code with git, but for
.larapilot/ files take the other side and re-run the Larapilot command that produced your
change rather than hand-editing the markers.
Branch
Purpose
main
Production-ready; tagged releases only
develop
Integration branch for the next release
feature/US-XXX-short-desc
One user story or cohesive feature; branch from develop
release/x.y.z
Release prep — version bump, changelog, final QA; merge → main + back-merge →
develop
hotfix/x.y.z
Urgent production fix; branch from main; merge → main +
develop
In Gitflow modes: no direct commits to main or develop; one atomic Conventional
Commit per plan task. Push and remote PR updates run only when
git_mode is GITFLOW_PUSH (or the user asks). Critical production
defects
route through hotfix/* via /larapilot-bug. Mode table:
Git mode settings.
When release_mode=YES and Gitflow is active, specs assigned to an in_progress
release branch from release/x.y.z and merge back into that branch (see
/larapilot-release). Unassigned specs keep branching from
develop. Multiple release/* branches may run in parallel — Sarah confirms which
release context is active before starting work.
Status machine
Status
Meaning
Skill to advance
TODO
Spec exists in backlog, not yet planned
/larapilot-plan US-XXX
PLANNED
Technical plan written and validated
/larapilot-implement US-XXX
IN PROGRESS
Implementation underway — tasks ticking off
Skill completes tasks → auto REVIEW
REVIEW
Code delivered, awaiting acceptance
/larapilot-review US-XXX — or autopilot spec-approve when
auto_approve: YES
DONE
Approved — spec is shipped
Next US-XXX or /larapilot-ship
Transition guards
The workflow engine rejects invalid state changes — for example, you cannot implement before planning, or
approve before review. When you reject a review, the spec returns to TODO with your feedback attached so the agent can replan and
reimplement.
Human-in-the-loop
Discovery interviews use AskQuestion wizards. By default, review is yours — the agent proposes, you approve
or send back. No spec reaches DONE without explicit acceptance
unless you set auto_approve: YES in project
settings (opt-in for /larapilot-autopilot).
The PRD
The PRD is the product contract: what the product promises, to whom, and how well.
Everything downstream is measured against it. A story exists because a requirement does, a bug is a
promise that was not kept, and a review checks that it was. It is one Markdown file,
.larapilot/docs/PRD.md, written by inception and changed only through skills.
What is in it
Section
What it holds
Who uses it later
Elevator Pitch · Vision
The product in one paragraph, and what changes for its users if it works
Everyone. The dashboard's functional summary opens with it
User Personas
Who has the problem, their goals, and what they do today instead
Stories are written as a persona
User JourneysJ-001
One per way a persona gets value: trigger, steps, the state that means it worked, the ways
it fails. The core journey comes first
/larapilot-spec cuts one story per journey
Domain Model
The nouns of the product: entities, their states, how they relate, and a glossary. Not a
database schema
Criteria, plans, and class names use these words and no synonym
Functional RequirementsFR-001
One promise each: MoSCoW tag, journey, actor and trigger, behavior,
Done means bullets a tester can verify, what is not included, what it
depends on, where it came from
Acceptance criteria are built from Done means. Triage and bug measure a request against
them
Non-Functional RequirementsNFR-001
Quality targets as a table: category, a measurable target, what it applies to, how it is
verified
Become criteria on the stories they apply to. Review and ship judge against them
MVP Scope
The fixed choices — project kind, origin, delivery target, business model, prior-art
verdict, success signal, deadlines — then In Scope, Out of Scope, Future Phases
How much of the PRD becomes stories now. Economics prices from it
Risks & AssumptionsQ-001
The riskiest assumption, the kill condition, assumptions and how each is validated, risks,
open questions with an owner and a date, and everything left Not decided
An open question blocks the story that depends on it instead of being guessed
Technical Architecture
Every choice that was asked: who runs the server, support window, frontend topology, stack,
data store, integrations, tenancy
Plan, implement, and ship honor it instead of asking again
PRD Revision History
One row per change made after inception: date, the skill that made it, what changed
You, when you need to know why the PRD says what it says
The PRD is written in the language of your conversation. Ids and the MoSCoW labels stay as they are in
every language. A complete example is in the New product
walkthrough.
What validate-prd checks
Finding
Severity
Meaning
PRD_EMPTY · PRD_MISSING_SECTION
Error
One of the six required sections is missing: Elevator Pitch, Vision, User Personas,
Functional Requirements, MVP Scope, Technical Architecture. Headings are recognized in
English, Italian, Spanish, and French; any other language passes with six ##
headings
PRD_RECOMMENDED_SECTION
Warning
User Journeys, Domain Model, Non-Functional Requirements, or Risks & Assumptions is
missing — usually a PRD written by an older version
PRD_FR_MISSING_MOSCOW
Warning
A requirement has no **MoSCoW:** line, so spec cannot tell whether to build
it
PRD_DUPLICATE_ID
Warning
The same id is defined twice
PRD_DANGLING_REFERENCE
Warning
An id is cited and never defined. Ids named in the revision history are not counted
An error fails the command. A warning never does, so an older PRD stays valid — and
/larapilot-prd "Upgrade the PRD" clears the warnings.
How the PRD changes after inception
Three skills may change the PRD, and each one leaves a row in the revision history. Find what
happened in the first column.
What happened
Skill
What changes in the PRD
You want one new capability
/larapilot-feature
A new FR-XXX, written in full
It works as agreed, and you now want it different — a change request
/larapilot-feature
The requirement is edited in place: same id, new behavior, before and after in the
history
Something is broken, and the PRD or a story already promised it
/larapilot-bug
Nothing. The fix is a story; the report goes to
support/intake.md
Something is broken, and nobody ever wrote the promise down — a
requirement gap
/larapilot-bug
The missing sentence, where it belongs: a Done means bullet under the requirement, or an
NFR row. Never a requirement called "Fix …"
Priorities, scope, wording, a target, or a recorded decision must change — nothing is
new, nothing is broken
Nothing. These change how the product is built, not what it promises
The vision or the target user changes — a pivot
/larapilot-inception
Rewritten with the current PRD as input. Ids whose promise survives are kept
You cannot tell which of these it is
/larapilot-triage
Triage decides and runs the right skill
When in doubt, leave the PRD alone and write the story. A story can be corrected
next week. A PRD that collects fixes stops being a contract.
PRD.md — revision history after three months
## PRD Revision History
| Date | Trigger | Summary |
| --- | --- | --- |
| 2026-07-01 | larapilot-inception | Initial PRD |
| 2026-07-15 | larapilot-feature US-011 | Added FR-011 Export PDF (MoSCoW: Should) |
| 2026-07-15 | larapilot-bug → FR-003 gap | SSO must work on Safari 17+ (macOS/iOS) |
| 2026-08-02 | larapilot-prd — Re-scope + Sharpen | FR-009 retired; FR-006 Could → Must; NFR-001 p95 < 300 ms |
| 2026-08-20 | larapilot-feature US-019 (change request) | FR-011: PDF opens in the browser instead of downloading |
Revision kinds
/larapilot-prd starts by naming the kind of change, because the kind decides how many
questions it asks and how much of the backlog moves. One request can carry several.
Kind
What changes
Example
Effect on the backlog
Editorial
Wording only — no promise moves
Typos, a clearer sentence, a glossary term
None
Sharpen
A promise becomes testable or complete
Done means added to a requirement, a number for a quality target, an open question
answered, a Not decided settled
Criteria added to open stories
Re-scope
What is in, what is out, and when
A MoSCoW tag changes, something moves to Future Phases, the delivery target changes, a
requirement is retired
Stories created, deferred, or deleted
Re-model
The nouns and the paths
An entity is renamed, a state is added, a journey is split, a persona is added
Stories that use the old names are rewritten
Re-decide
A recorded decision
Business model, who runs the server, support window, topology, data store, the prior-art
verdict
Architecture stories. The quote is recomputed
Upgrade
The shape of the PRD, not its promises
An older PRD gains journeys, domain model, quality targets, risks
None
Pivot
The vision, the target user, or the core journey
A new audience
Not a revision — it goes to /larapilot-inception
Whatever the kind, nothing is written before the readback: Mark shows before and
after for each item, then asks Apply · Revise · Cancel. When a security, privacy, or
accessibility target goes down, Lars or Violet states the consequence first.
Walkthrough.
Ids are permanent
Stories, plans, commits, and developer docs cite FR-, J-,
NFR-, and Q- ids. A revision never breaks those citations.
You want to
What happens to the id
Drop a requirement
It is retired: the heading stays, tagged Won't, with a
**Retired:** line giving the date and the reason
Split a requirement in two
Two new ids are created. The original stays with
**Superseded by:** FR-012, FR-013
Merge two requirements
One survives and cites **Merges:** FR-007. The other is retired
Rename an entity or a state
It is renamed in every journey, requirement, and quality target in the same revision. The
glossary keeps the old word as formerly
Reorder or renumber
Never. A number is never reused either
The backlog follows
A changed promise is worth nothing if the stories still describe the old one.
larapilot:prd-impact lists the stories that cite the ids being changed, and says what each
one needs. What it needs depends on how far the story has gone.
The story is
action
What happens
TODO
update_spec
The story is rewritten. Same code, same status
PLANNED
update_and_replan
The story is rewritten, then planned again: its plan was written against the old
promise
IN PROGRESS
coordinate
You are told work is under way. Nothing changes without your consent
REVIEW
rework
The story goes back to TODO with the change as feedback
(spec-request-changes)
DONE
new_spec
Never reopened. A changed promise becomes a change request through
/larapilot-feature; a promise the code never met becomes a bug
Terminal
php artisan larapilot:prd-impact --ids=FR-004,J-001 # the stories that cite these ids
php artisan larapilot:prd-impact # the whole PRD: every id and the stories that cite it
Without --ids the command is a coverage report. untraced lists the ids no
story cites, and uncovered_must the Must requirements among them — the ones
that would ship unbuilt. /larapilot-spec runs it after every backlog change.
Feature, bug, or revision — side by side
/larapilot-feature
/larapilot-bug
/larapilot-prd
You say
"Add PDF export for invoices"
"SSO login fails on Safari"
"Drop the CSV export and say what fast means"
It asks
Priority, journey, size, mockups, where it goes in the backlog
Severity, environment, how to reproduce, which story it maps to
Only what it cannot work out from the PRD
It writes
One new story
An intake entry and a fix story, or a rework of a story in review
No story of its own. It rewrites the stories the change touches
The PRD
Gains or changes one requirement
Untouched, unless a requirement was never written down
Is the point of the skill
Before saving
A readback you confirm. Nothing is written before it
Then
/larapilot-plan → /larapilot-implement →
/larapilot-review on each story
Rule files: .larapilot/runtime-ops-1.md (PRD Living Document) and
runtime-ops-3.md (PRD Revision).
Artifacts
Everything the workflow produces lives in .larapilot/ inside your repo. Skills
read and write these files; they survive across editor sessions and give every agent the same ground truth.
/larapilot-inception, /larapilot-adopt; then
/larapilot-feature, /larapilot-bug (requirement gap only),
/larapilot-prd
The product contract: personas, journeys, domain model, requirements, quality targets,
scope, risks, architecture. See The PRD
backlog.yaml
/larapilot-spec
Ordered list of specs with status, priority, and traceability to FRs
specs/US-XXX.yaml
/larapilot-spec, shortcuts
User story body, acceptance criteria, epic link, priority
plans/US-XXX-plan.yaml
/larapilot-plan
Task breakdown with Git deliverables, test data, and dependencies
mockups/{spec}/
/larapilot-design
Static HTML previews at /mockups/{spec} (dev/staging); gallery at
/larapilot/design (presentation index + zip of HTML/assets); linked on spec
detail and in the JSON API when present
internal-feedback/{code}.md
Dashboard · larapilot:spec-comment
Append-only PM/dev notes until the spec is DONE; blocking
comments flagged with [blocks-merge]
design-systems/
larapilot:install · larapilot:update
Packaged mockup references + optional custom folders — see design systems
usage/
skills (Lucille) · /larapilot-usage
ledger.jsonl + schedule.yaml — tokens, minutes, deadlines; see
Usage
Account profile, the last computed quote, researched competitors, and the client quote — see
Economics
choices.yaml
inception · larapilot:choices-set
Snapshot of the fixed choices made at inception — kind, target, business model, prior-art
verdict, success signal, kill condition, operations, topology, stack — for the dashboard and
Economics
integrations.md
larapilot:install · larapilot:update
Setup guide for optional forges + chat notifications
Laravel app code
/larapilot-implement
Models, controllers, views, migrations, Pest tests — outside .larapilot/
JSON responses from CLI commands use schema larapilot/v1 — skills parse these envelopes to
confirm writes and status changes. Inspect artifacts visually via the dashboard or programmatically via the API.
Developer domain docs
The code says what; the PRD says what the client wanted. Neither says why it
is built this way. .larapilot/docs/devs/ is where that reasoning survives the session
that produced it: one Markdown file per domain, written for whoever maintains the code next. It is
always on — there is no setting for it.
.larapilot/docs/devs/
README.md # index — one row per domain, last spec that touched it
TEMPLATE.md # the skeleton every file follows
billing.md
user-authentication.md
webhook-ingestion.md
The skeleton
Section
What it carries
Metadata table
Status · Introduced by · Last updated by ·
Owner persona
Purpose
What the domain is responsible for, in business terms, and what breaks for the user without
it
Functional flow
Runtime behavior step by step, from the entry point to the outcome — failure paths
included
Technical design
Models, services, actions, jobs, events, routes, commands, config keys, tests — and where they
live
Architectural choices
What was chosen, what was rejected, and why — so nobody undoes a constraint
by accident
Key decisions & invariants
The rules that must stay true, and what breaks when they don't. References
decisions.yaml ids
Extension points & gotchas
Where new behavior plugs in, and the traps
Related
Specs, plans, review files, other domains
Rules that keep them useful
Always English, whatever language the PRD and the conversation use — the files
address whoever inherits the codebase, not the client.
Updated in the same spec that changes the behavior, inside the task commit — code,
tests and doc together. A task is not task-done while its doc describes the old code.
Never deferred.effort: ECO makes the prose terse; the file is still
written.
One file per domain, not per class. An entity gets its own file only when it carries
behavior beyond CRUD.
Rewrite, don't append. The folder is the current truth; history belongs to Git.
Who enforces it
Skill
What it does with domain docs
/larapilot-plan
Names, per task, the domain file it must leave current (## Domain Docs in the task
template)
/larapilot-implement
Writes or updates the file before each task commit. Robert flags a domain whose code moved
without its doc as a High finding (stale dev doc)
/larapilot-review
Blocks on a touched domain with no current file — under ECO too
/larapilot-ship
Blocks the release checklist on a stale or missing doc for a shipped feature
/larapilot-bug
Corrects the doc when a fix changes behavior
/larapilot-adopt
Writes a file for every domain the codebase analysis found, so a brownfield project reaches its
first spec already documented
A project with no docs is brought level on the first change
config-show (and doctor) report the state under data.dev_docs:
path, documented, count, and the domains already
written. When documented is false and the codebase already has domains, the
first spec, fix, or hotfix inventories every existing domain, writes a file for each,
commits the backfill on its own — docs(US-XXX): bring developer domain docs level — and only
then runs its own work. No question asked, no partial pass, no ECO exemption. Where the
original reasoning can't be recovered from git history, the PRD, decisions.yaml, or the
plans, the file says <!-- TODO: verify --> instead of inventing a motive.
Not the same as docs/handbook/. The
project handbook is an opt-in manual for the whole project,
mixed technical and functional. docs/devs/ is engineering-only, mandatory, and the only
place that records implementation rationale. Contract:
.larapilot/runtime-dev-docs.md · path key paths.dev_docs.
Usage & Lucille
Lucille keeps a committed ledger of AI tokens and wall-clock time, plus
delivery deadlines. Every skill may append an entry at session end; /larapilot-usage
interrogates the data. Dashboard Usage is the token and hour ledger;
Plan is the Gantt and the deadlines. Usage still offers the Markdown download.
The Usage page opens with what the delivered specs took. For every spec that is
DONE: the hours it was estimated at — the hours of its plan's tasks, its
story points when it has no plan, before the PM/QA buffer: the same estimate Economics prices — the
time it took to build, how long it then waited in review, and the
tokens the ledger holds for it. A spec that review sent back, or that was started over,
says so. Above the table: the estimated hours, the build time, and how many times the first holds the
second — given from three specs with a build time, since one spec proves nothing.
fernway.test/larapilot/usage
On Fernway: the project, then each delivered story with its estimate, build time, ratio, wait in review, and tokens.
The times are not logged by anyone: spec-start, spec-review, and
spec-approve already write the moment of every step in the backlog, and the page reads
them. So they cost no tokens, and they are there with Lucille off. What they are is worth keeping in
mind. Build is the time the spec was IN PROGRESS, pauses included: a
session left open overnight counts the night. In review is a wait, not the hours
someone spent reviewing. And the estimate is the agent's own. The figure says how the plans compare
with the builds; it does not say what a person would have taken. Only the tokens come from the ledger,
which is why Lucille logs with --spec= whenever a session works one spec.
The same figures: a section of report.md, insights.actuals in
usage-report --insights, build in GET /larapilot/api/metrics,
and one line on Economics — for you, never in the client quote.
When Lucille is OFF
usage-log refuses writes. /larapilot-usage states she is excluded and may still
run a read-only report on historical data. Re-enable:
php artisan larapilot:settings-set --lucille=YES.
ECO switches set lucille: NO automatically unless you pass --lucille=YES together
with --effort=ECO.
Dashboard: /larapilot/usage for tokens and estimate vs build · /larapilot/plan for the Gantt and
deadlines · download /larapilot/usage/report.md (dev/staging only). MCP
RunArtisanTool allows larapilot:usage-report.
Laravel, PHP & DB upgrades
Three skills move the project to a new version: /larapilot-laravel-upgrade,
/larapilot-php-upgrade, and /larapilot-db-upgrade. All three start with a
readiness report and its criticalities — blocker,
high, medium, low, info — and change nothing until you
choose upgrade now, add it to the backlog, or stop at the report.
Dependencies — composer.lock, and Packagist for the releases of each direct
dependency that support the target Laravel on the PHP that Laravel needs. It follows
self.version into the packages of the same vendor, so Filament is measured by
filament/support. Verdicts: ok, update (fits the constraint),
bump (a new constraint, often a major), blocker, abandoned,
private (Nova, Spark, a Satis — Packagist cannot see it). Answers are cached for a day.
PHP — the PHP the project is on (config.platform.php, else the floor of
require.php), every installed package whose PHP requirement excludes the target with what
requires it, code the target deprecates (implicitly nullable parameters on 8.4, non-canonical casts on
8.5, …), extensions that left core.
Database — raw SQL, column types, reserved words, and server options the target does
not accept, with file and line (MySQL functions on the way to PostgreSQL, GROUP BY … DESC on
MySQL 8.0, default-authentication-plugin on 8.4, …), and the checklist no scan can see.
Pins — Dockerfile and compose images, CI matrices, vapor.yml,
.php-version, .nvmrc, phpstan.neon, rector.php.
Composer has the last word: the report names the --dry-run that asks it. The reports go to
.larapilot/docs/upgrades/ (paths.upgrades).
Upgrade now
A clean tree, a branch by git_mode, and a baseline: tests, Pint, Larastan, the build.
PHP first when the target Laravel needs it; one Laravel major at a time; the database
last, once the code runs on the target engine locally.
Each step: the Composer change, the upgrade guide of that version (Boost Search Docs, never
from memory), the package playbooks, Rector when the project has it, the gates, one commit.
An upgrade report: criticalities resolved, followed up, or accepted; dependencies before and after;
the deploy runbook; the rollback.
/larapilot-db-upgrade never connects to a production or shared database: it rehearses on a
scratch database and writes the data move (pgloader with sequences reset, counts to compare) and the
cutover. The About page of the dashboard shows each version with its support window and
the command that checks the next upgrade.
fernway.test/larapilot/about
The About page of the dashboard: each version against its support window.
Forges & notifications
Optional remote forges and chat fan-out — all OFF by default, orthogonal to
git_mode. Full install steps live in .larapilot/integrations.md (published on
install/update). Toggle via /larapilot-settings or larapilot:settings-set.
Remote forges
Setting
Tooling
Probe
github
gh CLI
larapilot:github-status
gitlab
glab CLI (MR)
larapilot:gitlab-status
bitbucket
Bitbucket Cloud REST (access token or app password)
larapilot:bitbucket-status
azure
Azure CLI (az repos + azure-devops ext) or REST (PAT)
larapilot:azure-status
When ON, skills open/update the PR/MR (still respecting git_mode), always print the URL, and
may emit pr_opened / pr_updated notifications. Secrets stay in .env
— never in config.yaml.
Optional HTTP Basic Auth on the dashboard UI, via the
dashboard_auth setting (default NO).
UI-only — never /larapilot/api/* or MCP.
manage dashboard users
php artisan larapilot:dashboard-user add andrea # prompts for the password (or --password=)
php artisan larapilot:dashboard-user list
php artisan larapilot:dashboard-user remove andrea
php artisan larapilot:settings-set --dashboard-auth=YES
Credentials are argon2id/bcrypt hashes in .larapilot/auth.yaml — no database, no
User model; the file is added to .gitignore automatically.
Env: LARAPILOT_DASHBOARD_AUTH_REALM, LARAPILOT_DASHBOARD_AUTH_MAX_ATTEMPTS
(failed sign-ins per minute per IP, default 30; 0 disables throttling).
dashboard_auth=YES with no users → dashboard fails closed (HTTP 503) on a notice
that tells how to add the first one. Serve over
HTTPS on shared hosts.
Larapilot is repo-level; Backstage is org-level. The integration publishes
.larapilot/ into the developer portal — a catalog entity, a TechDocs site built from the PRD
and backlog, and a live delivery snapshot. The direction is one-way: the workspace stays the source of
truth, and workflow state never changes from the portal.
When it is worth it
Situation
Recommendation
No Backstage in the organization
Skip it — the dashboard already covers a single repo
Backstage exists, one Laravel repo
Catalog entity + TechDocs — the PRD and backlog become discoverable next to every other service
Backstage with several Larapilot repos
The real payoff — an entity provider polling /backstage gives one org-wide view of
delivery state
Generate
Editor
/larapilot-backstage
The skill asks for owner, system, and lifecycle,
persists them to .env, then generates. Equivalent CLI:
Component entity, plus one API entity per OpenAPI contract found
(storage/api-docs/api-docs.json, openapi.json,
docs/openapi.json, .larapilot/openapi-product.json)
Only with --force
mkdocs.yml (repo root)
TechDocs config — docs_dir: .larapilot/techdocs, plugin
techdocs-core, nav over PRD and backlog
The two root files may already belong to the project (an existing catalog entry, an existing MkDocs site),
so Larapilot keeps them unless you pass --force; the envelope's data.hint names
what was kept. Everything under .larapilot/techdocs/ is generated output — fix the PRD or the
spec and regenerate rather than editing a page.
Catalog entity
catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: acme-shop
namespace: default
title: 'Acme Shop'
description: 'A thing.' # PRD elevator pitch, when present
annotations:
backstage.io/techdocs-ref: 'dir:.'
larapilot.io/version: 2.2.0
larapilot.io/workspace: .larapilot
larapilot.io/prd: .larapilot/docs/PRD.md
larapilot.io/board-url: 'https://staging.acme.test/larapilot'
larapilot.io/api-url: 'https://staging.acme.test/larapilot/api'
tags: [laravel, larapilot]
spec:
type: service
lifecycle: production
owner: 'group:default/platform'
system: commerce
providesApis:
- acme-shop-api
---
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: acme-shop-api
title: 'Acme Shop API'
spec:
type: openapi
lifecycle: production
owner: 'group:default/platform'
definition:
$text: ./openapi.json # resolved by Backstage relative to this file
The name defaults to a slug of app.name, the description to the PRD
Elevator Pitch (falling back to the composer description), and the
larapilot.io/* annotations let a plugin locate the board and API for this entity.
Catalog identity
Identity describes the org catalog, not the delivery workflow — so it lives in Laravel config and
.env, not in .larapilot/config.yaml. Never set it with
larapilot:settings-set.
Env var
Default
Purpose
LARAPILOT_BACKSTAGE_ENABLED
true
Master switch — false also hides the API endpoints
LARAPILOT_BACKSTAGE_OWNER
guests
Set this. Backstage Group/User that owns the entity (platform or
group:default/payments); unresolvable owners show as dangling refs
LARAPILOT_BACKSTAGE_SYSTEM
—
Parent System entity, when your org models them
LARAPILOT_BACKSTAGE_LIFECYCLE
experimental
experimental · production · deprecated
LARAPILOT_BACKSTAGE_COMPONENT_TYPE
service
service · website · library
LARAPILOT_BACKSTAGE_NAME
slug of app.name
Entity name override (also _TITLE, _DESCRIPTION,
_NAMESPACE)
LARAPILOT_BACKSTAGE_BASE_URL
app.url
Base URL for catalog links and annotations — non-production only
LARAPILOT_BACKSTAGE_TECHDOCS
true
Generate the TechDocs site alongside the catalog entity
LARAPILOT_BACKSTAGE_WORKFLOW_API
false
Also register the dev-only Larapilot API as its own API entity — tooling, not a
product contract
php artisan larapilot:config-show reports the resolved mapping under
data.backstage (entity ref, owner, system, lifecycle, TechDocs paths, whether the catalog file
exists).
TechDocs
MkDocs config and sources are generated together, so Backstage builds the docs site straight from the
repository:
Specs and plans are YAML on disk, so each story page is rendered to Markdown: status/priority/points/epic
table, task progress, internal-feedback counts, then the spec body, the technical plan, and the task
checklist. Internal feedback bodies stay in the repo — only counts are published.
Live delivery data
For a Backstage frontend plugin or entity provider, two endpoints share the
API gate (dev/staging only):
Endpoint
Use
GET /larapilot/api/backstage
Entities, rendered YAML, TechDocs metadata, and snapshot — metrics, per-status
counts, blocking feedback, and a story list without spec bodies or plan text, so a portal
can poll many repos cheaply
GET /larapilot/api/backstage/catalog-info.yaml
The same entities as a YAML descriptor, consumable as a Backstage url location
GET /larapilot/api/backstage → data.snapshot (excerpt)
Proxy the API, never expose it. Call these endpoints through the
Backstage backend proxy so LARAPILOT_API_TOKEN stays server-side — never from
browser code, and never against a production host (the API returns 404 there by design). If
the portal cannot reach a dev/staging environment, ship the committed catalog-info.yaml and
TechDocs instead of the live endpoints.
Register & keep fresh
Commit catalog-info.yaml, mkdocs.yml, and
.larapilot/techdocs/ on the default branch.
In Backstage: Create → Register existing component → paste the repo URL of
catalog-info.yaml. Orgs with catalog discovery configured skip this step.
TechDocs builds from mkdocs.yml; the backstage.io/techdocs-ref: dir:.
annotation is already set.
Regenerate after PRD, backlog, or plan changes — a CI job on the default branch running
--write --force and committing the diff keeps the portal from drifting.
Personas & boundaries
Matt — owns the catalog mapping: owner, system, lifecycle, which APIs get registered
Jack — CI regeneration and environment reachability
Albert — TechDocs nav and readability
Lars — token/proxy boundary; the portal is never pointed at production
The portal renders; it does not drive. Backstage is a read surface
over .larapilot/. Scope changes still go through
/larapilot-feature and /larapilot-prd, and status
still moves only through skills and Artisan.
Project trackers
Optional, API-key based sync between the backlog and the tool the rest of the organisation
already lives in — Linear,
Asana,
Jira,
Trello,
ClickUp, or
Monday. A PM or a client follows delivery
without ever opening backlog.yaml. The workspace stays the source of truth; the tracker is a
window, not a second workflow.
When it is worth it
Situation
Recommendation
Solo dev, no external stakeholders
Skip it — the dashboard at /larapilot already shows the board
Client or PM tracks work in their own tool
Push the backlog so they see progress where they already look
Team runs sprints in Jira/Linear alongside the code
Push after planning, pull before standup to catch drift
You want the tracker to drive the workflow
Not supported by design — status moves through skills and Artisan
Setup
/larapilot-tracker
The skill picks the provider, tells you where to generate the API key, writes it to .env,
validates the status map against the real board, dry-runs, then pushes. By hand:
php artisan larapilot:tracker-status --ping # credentials + target board
php artisan larapilot:tracker-push --dry-run # what would change, no API calls
php artisan larapilot:tracker-push # backlog → tracker
php artisan larapilot:tracker-pull # tracker → drift report (read-only)
php artisan larapilot:tracker-pull --apply # write mapped statuses back
What gets mirrored
Provider
Auth
Destination
Plan tasks become
Status maps to
Linear
personal API key
team key (ENG)
sub-issues
workflow state
Jira (Cloud, REST v2)
email + API token
project key
subtasks
status, via a workflow transition
Asana
personal access token
project gid
subtasks
section (DONE also marks complete)
Trello
key + token
board id
checklist items
list (board column)
ClickUp
personal token pk_…
list id
subtasks
list status
Monday
API token
board id
subitems
status-column label
A user story becomes an issue titled US-XXX — Title carrying the spec body, priority, points,
and epic. Plan tasks are mirrored as native subtasks, not a checklist buried in the
description. One provider is active at a time, but links are stored per provider — switching tools, or
switching back, never loses the mapping.
Push writes, pull reports
The direction is deliberately asymmetric. Push is authoritative: .larapilot/ decides what a
story says and which column it sits in, and unchanged stories are skipped without an API call. Pull is a
report — it lists drift and changes nothing until you pass --apply.
DONE is never applied from a tracker. DONE is a human review gate that records the
merge commit — it stays with /larapilot-review and larapilot:spec-approve.
Spec text is never read back. Titles, bodies, and acceptance criteria are owned by
.larapilot/; the card description says so, and edits made in the tracker are overwritten on
the next push.
TODO and PLANNED sharing one column is normal and is not
drift — a story is in sync when its forward mapping matches the remote label.
A remote status outside the map is reported as drift with no suggestion, never guessed at.
Set LARAPILOT_TRACKER_PULL_COMMENTS=true to import tracker comments as internal feedback —
non-blocking, and imported once each.
Configuration
Env var
Default
Purpose
LARAPILOT_TRACKER_ENABLED
false
Master switch for the integration
LARAPILOT_TRACKER_PROVIDER
—
linear · asana · jira · trello ·
clickup · monday
LARAPILOT_TRACKER_SYNC_TASKS
true
Mirror plan tasks as native subtasks
LARAPILOT_TRACKER_PULL_COMMENTS
false
Import remote comments as internal feedback
LARAPILOT_MONDAY_DESCRIPTION_COLUMN
—
Monday items have no description field — point this at a long-text column
Per-provider credentials follow the same shape:
LARAPILOT_LINEAR_API_KEY / _TEAM,
LARAPILOT_JIRA_BASE_URL / _EMAIL / _API_TOKEN / _PROJECT,
LARAPILOT_ASANA_TOKEN / _PROJECT,
LARAPILOT_TRELLO_KEY / _TOKEN / _BOARD,
LARAPILOT_CLICKUP_TOKEN / _LIST,
LARAPILOT_MONDAY_TOKEN / _BOARD. Status maps live in
config/larapilot.php → tracker.providers.{provider}.status_map. If a mapped
column does not exist, the push fails and names the columns that do — Larapilot never creates columns in
your tracker.
Personas & boundaries
Matt — provider choice, status mapping, link hygiene
Mark — what non-developers should see on the board
Jack — the CI push step on the default branch
Lars — the credential boundary: API keys live in .env, never in
.larapilot/
Credentials never enter the repo. The tracker key is
write-capable on a third-party workspace — a tighter boundary than the read-only Larapilot API. It
belongs in .env and CI secrets. config-show and tracker-status
report whether a credential is present, never its value. .larapilot/tracker.yaml is
committed on purpose — it holds the spec → remote-id map so the team shares one mapping instead of each
machine creating duplicate cards, and it contains identifiers only.
Aikido
Optional. Aikido
scans the repository on its side — dependencies, code, leaked secrets, infrastructure, licenses —
through its connection to the git provider. Larapilot runs no scanner and installs nothing: it
reads what Aikido found over the public REST API, says what was already decided about
each finding, and brings the rest into the workflow through /larapilot-triage.
Setup
Connect the repository in Aikido, through the git provider (GitHub, GitLab,
Bitbucket, Azure DevOps). Larapilot cannot do this step.
Create the credentials in Aikido under Settings → Integrations → Public REST
API. Reading needs the issues:read and repositories:read scopes;
telling Aikido a decision needs issues:write; asking for a scan needs
repositories:write.
Put them in .env — never in .larapilot/, which is
committed.
Turn the setting on and check the connection.
.env
LARAPILOT_AIKIDO_CLIENT_ID=
LARAPILOT_AIKIDO_CLIENT_SECRET=
LARAPILOT_AIKIDO_REGION=eu # eu (default) · us · au · me
LARAPILOT_AIKIDO_REPOSITORY= # id or name in Aikido, for this machine; empty = chosen or found from the git remote
LARAPILOT_AIKIDO_FAIL_ON=high # critical · high · medium · low · none
LARAPILOT_AIKIDO_PUSH_DECISIONS=true # false = decisions stay in the project
LARAPILOT_AIKIDO_CACHE=300 # seconds the dashboard keeps what it read
LARAPILOT_AIKIDO_BASE_URL= # only when the workspace is reached through another address
aikido-status answers with enabled, configured,
authenticated, the repository it matched (id, name, branch, last scan), and
hints: one line for each thing that is missing, with what to do about it.
The repository
The repository is found from the git remote, by its address or by its name. When the code is scanned
under another repository — a fork, a mirror, a monorepo, a name that differs — nothing matches:
aikido-status answers needs_repository: true, and you are asked
which one it is. /larapilot-aikido asks in chat, /larapilot/security
shows the list with a form, and the terminal has a command. Nothing is chosen for you: a name that
looks alike is not a match.
Terminal
php artisan larapilot:aikido-repos # the repositories of the workspace
php artisan larapilot:aikido-repos --search=shop # only the names that hold these letters
php artisan larapilot:aikido-repos --use=12 # this project is repository 12 (or its exact name)
php artisan larapilot:aikido-repos --forget # back to the git remote
The choice is kept in .larapilot/aikido.yaml, which is committed, so every machine reads
the same repository. LARAPILOT_AIKIDO_REPOSITORY in .env names it for one
machine, and wins there.
The skill
in the editor
/larapilot-aikido
Status — one line: repository, branch, last scan, the severity the gate fails
on. With something missing, the hints are printed and the skill stops.
Download — aikido-issues --new --report. The findings nobody decided
about are shown in a table, the most severe first, 15 rows at most; the detail of all of them goes
to .larapilot/docs/security/aikido.md.
Scope — one question: the findings that stop the ship gate, every new finding,
the ones you name, or none.
Confirm — finding by finding (or in batches when there are many): resolve, waive
with a reason, or skip for now. Waives are recorded immediately with
aikido-link.
Plan — aikido-plan --ids=… groups the confirmed ids by kind and fix;
you approve the groups. Leaked secrets never merge with anything else.
Hand off to triage — each group goes to /larapilot-triage in the
same turn, with the block below. Triage measures it against the PRD like any request: a known
vulnerability in shipped code is a bug when a requirement names security for that
area, a bug with a requirement gap otherwise, and the verdict is not asked.
/larapilot-bug then writes the fix spec, with a criterion that Aikido no longer
reports the finding.
Record — aikido-link 24 --spec=US-012, so the finding is not handed
over again, on this machine or another.
what triage receives
Aikido finding
ids: 24, 25
severity: critical (95/100)
kind: Vulnerable dependency
title: guzzlehttp/psr7
where: fjord-invoices
cves: CVE-2026-1111
fix: Upgrade guzzlehttp/psr7 to 2.7.0 or later
What a finding can be
State
Meaning
At the ship gate
new
Open in Aikido, and nobody decided about it
Counts
in_backlog
A spec of the backlog fixes it. Not fixed yet
Counts, until Aikido no longer reports it
waived
Accepted as it is, with a reason of at least a sentence, given by the user
Does not count
no longer open
Decided about here, and Aikido reports it as open no more: fixed, or waived here and
ignored there
Does not count
Decisions are told to Aikido
What you decide here is sent to Aikido, so the workspace says the same as the project.
Decision
In Aikido
aikido-link 40 --waive --reason="…"
The finding is ignored, with the reason as its comment. It leaves the
open findings at once
aikido-link 24 --spec=US-012
A note on the finding: the spec that fixes it. The finding stays open
aikido-link 40 --forget
The waiver is taken back: the finding is open again
A finding can be in several repositories of the workspace. When it is, only the issues of
this repository are ignored, and the other projects keep theirs.
The credentials need the issues:write scope. When Aikido refuses, or cannot be
reached, the decision is kept and listed as unsent;
larapilot:aikido-push tells it later. It also tells the decisions taken with an
earlier version.
A waiver Aikido would not take back is not forgotten here, so the two never disagree.
--local on aikido-link keeps one decision in the project;
LARAPILOT_AIKIDO_PUSH_DECISIONS=false keeps them all.
The register for the client
larapilot:aikido-register writes {paths.security}/aikido-register.md, and
Register for the client (.md) on /larapilot/security downloads it: one
document with every finding of the repository — open with the fix that is planned,
resolved with the date, ignored with the date and the reason — and
a count by severity. It is what a client or an auditor asks for when a certification requires that
every vulnerability is tracked to a decision, and it is written in the language of the PRD.
The reason of a finding waived here is the one you wrote. Aikido does not give back the reason of a
finding ignored there by hand: the register lists it, and says the reason is kept in Aikido.
The verdict of the gate is FAIL when a finding at
LARAPILOT_AIKIDO_FAIL_ON (default high) or above counts,
WARN when only lower ones do, PASS otherwise. /larapilot-ship
runs larapilot:aikido-issues --gate --report: FAIL is a release blocker,
WARN a note in the assessment. The same command exits 1 on
FAIL, so a pipeline can use it.
Commands
Command
What it does
larapilot:aikido-status
Setting, credentials, repository, last scan, hints. Read-only
larapilot:aikido-issues
The open findings with their state and the verdict of the gate.
--new only the ones with no decision · --severity=high that
severity and above · --type=open_source one kind ·
--limit= · --report writes
{paths.security}/aikido.md · --gate exits 1 on
FAIL
larapilot:aikido-plan --ids=24,31
Groups confirmed ids by kind and fix for triage handoffs. Read-only
larapilot:aikido-link 24,25 --spec=US-012
The spec that fixes the findings, and a note about it in Aikido. A number Aikido does not
report is refused
larapilot:aikido-link 40 --waive --reason="…"
Accepts a finding as it is, keeps why, and ignores it in Aikido with that reason
larapilot:aikido-link 40 --forget
Drops what was decided, and takes a waiver back in Aikido: the finding is
new again. --local on any of the three tells Aikido nothing
larapilot:aikido-push
Tells Aikido the decisions it was not told yet
larapilot:aikido-repos
The repositories of the workspace. --search= narrows the list ·
--use=12 keeps the one this project is · --forget drops the
choice
larapilot:aikido-register
Writes the register for the client to
{paths.security}/aikido-register.md
larapilot:aikido-scan
Asks Aikido to scan the repository again. The scan runs there and takes minutes
aikido-status, aikido-issues, aikido-plan, and
aikido-repos (the list only) are allowed through the MCP RunArtisanTool;
the commands and the options that write are not.
On the dashboard
/larapilot/security is always in the menu. With the setting off it is two lines: what
Aikido scans, and how to turn the link on. With the setting on it opens on the verdict
of the gate, in a sentence — A release is stopped: 3 open findings are high or above, 2 with no
decision yet — then the open findings by severity, then every finding, the most severe first.
A row opens on what the finding is, how to fix it, where it is, its CVE, when it was first seen, and
what was decided: the spec it is in the backlog as, with its status, or the reason it was waived.
Findings filter by decision and by text. What was read is kept for five minutes; Read
again asks Aikido anew, Download report (.md) saves the same document
the skill writes, and Register for the client (.md) saves every finding — open,
resolved, ignored with its reason. A decision Aikido was not told yet is said under the list. When
the git remote matches no repository of Aikido, the page lists the repositories of the workspace
and asks which one the project is. When Aikido cannot be read the page says what to check, and the
rest of the dashboard is untouched.
What is kept, and where
What
Where
Committed
Client id and secret
.env
Never
Access token
The cache, for as long as Aikido says it lasts, less a minute
Never written to a file
Decisions: finding id, spec, reason of a waiver, whether Aikido was told. The repository
that was chosen
.larapilot/aikido.yaml
Yes — the team shares one set of decisions
The register for the client: every finding, open, resolved, or ignored
.larapilot/docs/security/aikido-register.md
Your call: it names vulnerabilities that are still open
The report: every open finding with its decision
.larapilot/docs/security/aikido.md
Your call: it names vulnerabilities that are still open
What Larapilot never does. It never marks a finding as fixed: a
finding leaves the list when Aikido no longer reports it, after the fix is merged and scanned. It
never waives a finding: only the user does, with a reason — and only then is Aikido told to ignore
it. It never chooses the repository: when the git remote finds none, you name it. It never rates
a finding again: the severity is Aikido's. The address of the token endpoint is taken from the region
(https://app.{region}.aikido.dev/api/oauth/token, and
https://app.aikido.dev for eu).
Production errors
Optional. The running application throws, one tracker records it,
and Larapilot reads the open errors back: it puts them together into bugs, says
what was already decided about each, and brings the rest into the workflow through
/larapilot-triage. settings.errors turns it on and
settings.errors_provider names the tracker: /larapilot-error asks which one
when none is set. The skill (/larapilot-error) and the commands
(larapilot:errors-*) are the same for every tracker; the names the commands had when
Boogle was the only one (larapilot:boogle-*) still answer, and the ledger keeps its
name, .larapilot/boogle.yaml.
The trackers
Provider
What is read
In .env
Closed from here
boogle — the default
Every throw the self-hosted
Boogle recorded,
and the outages its uptime monitor found
The error lines of an AWS CloudWatch log group, through the AWS CLI signed in on the
machine
LARAPILOT_CLOUDWATCH_LOG_GROUP
No
Every credential is one that reads the tracker — never the key the application
reports with (FLARE_KEY, BUGSNAG_API_KEY, ROLLBAR_TOKEN,
HONEYBADGER_API_KEY). SENTRY_AUTH_TOKEN, SENTRY_ORG,
SENTRY_PROJECT, DD_API_KEY, DD_APP_KEY, and
DD_SITE are taken when the project has them already. One tracker is read at a time.
Laravel Nightwatch is not among them: it publishes no API to read exceptions with.
One row is a throw — Boogle, CloudWatch, the logs of Datadog. Larapilot puts
together the rows that share the exception, the file, and the line, counts them, and draws
them day by day on the dashboard.
One row is a bug — Sentry, Bugsnag, Flare, Rollbar, Honeybadger, Datadog
Error Tracking. The tracker grouped already: its grouping and its count are kept. The
dashboard has no day chart, since such a tracker says when a bug was last thrown and not each
time.
Setup with Boogle
Have the application send its exceptions to Boogle:
composer require andreapollastri/boogle-client, then
php artisan boogle:install. Larapilot does not do this step.
Create a token in Boogle, as an admin user, in the profile
under API tokens. The admin API answers to admin users only.
Put the address and the token in .env — never in
.larapilot/, which is committed.
Turn the setting on and check the connection.
.env
LARAPILOT_BOOGLE_URL=https://boogle.example.com # empty = taken from BOOGLE_SERVER
LARAPILOT_BOOGLE_TOKEN= # the token of an admin user of Boogle
LARAPILOT_BOOGLE_PROJECT= # id or title in Boogle; empty = found by itself
LARAPILOT_BOOGLE_CACHE=300 # seconds the dashboard keeps what it read
errors-status answers with enabled, provider,
configured, authenticated, the project it matched (id, title,
address, group, whether the uptime is watched), remote_resolve, and
hints: one line for each thing that is missing. The project is the one named in
LARAPILOT_BOOGLE_PROJECT; with nothing named, the one whose key is
BOOGLE_PROJECT_KEY — what the client package sends with — and then the one whose
address is APP_URL.
Setup with another tracker
The application sends its exceptions to the tracker as it already does — Larapilot changes
nothing there. Put what reads the tracker in .env, name the tracker, and check: with
the errors on, errors-status asks the tracker, so a token it refuses is known before
the skill runs.
.env — the variables of the tracker the project uses
# Sentry — an auth token with event:read (event:write to close an issue)
LARAPILOT_SENTRY_AUTH_TOKEN= # or SENTRY_AUTH_TOKEN
LARAPILOT_SENTRY_ORGANIZATION= # or SENTRY_ORG: the slug of the organization
LARAPILOT_SENTRY_PROJECT= # or SENTRY_PROJECT: the slug of the project
LARAPILOT_SENTRY_URL=https://sentry.io # self-hosted, or a region: https://de.sentry.io
# Bugsnag — a personal auth token (My account → Personal auth tokens)
LARAPILOT_BUGSNAG_AUTH_TOKEN=
LARAPILOT_BUGSNAG_PROJECT_ID= # Project settings → General
# Flare — a personal access token with the read scope (write to resolve an error)
LARAPILOT_FLARE_TOKEN=
LARAPILOT_FLARE_PROJECT_ID=
# Datadog — an API key and an application key
LARAPILOT_DATADOG_API_KEY= # or DD_API_KEY
LARAPILOT_DATADOG_APP_KEY= # or DD_APP_KEY
LARAPILOT_DATADOG_SITE=datadoghq.com # or DD_SITE: datadoghq.eu, us3.datadoghq.com, …
LARAPILOT_DATADOG_SERVICE= # the service tag; empty = APP_NAME
LARAPILOT_DATADOG_SOURCE=error_tracking # or logs: the error logs of two weeks
LARAPILOT_DATADOG_TRACK=trace # in Error Tracking: trace (APM), logs, rum
# Rollbar — a project access token with the read scope (write to resolve an item)
LARAPILOT_ROLLBAR_ACCESS_TOKEN=
# Honeybadger — the personal authentication token of a user (profile → Authentication)
LARAPILOT_HONEYBADGER_AUTH_TOKEN=
LARAPILOT_HONEYBADGER_PROJECT_ID= # as in the address of the project page
# AWS CloudWatch Logs — read with the AWS CLI signed in on the machine: no key is kept
LARAPILOT_CLOUDWATCH_LOG_GROUP= # the log group the application writes to
LARAPILOT_CLOUDWATCH_REGION=eu-west-1 # or AWS_DEFAULT_REGION
LARAPILOT_CLOUDWATCH_PROFILE= # optional: a profile of the AWS CLI
LARAPILOT_CLOUDWATCH_FILTER="?ERROR ?Exception ?CRITICAL"
# Every tracker
LARAPILOT_ERRORS_CACHE=300 # seconds the dashboard keeps what it read
LARAPILOT_ERRORS_TIMEOUT=15 # seconds a call to the tracker may take
*_PROJECT_NAME (Bugsnag, Flare, Rollbar, Honeybadger) is optional, and gives the
report its title. A provider Larapilot does not know, written by hand in
config.yaml, is named by errors-status with the ones it does.
One entry for each bug
Boogle keeps one row for each time an exception is thrown, and gives each a code
(#BUG12, #OUT3). Fourteen rows of one exception at one line are one bug
thrown fourteen times, and that is how Larapilot shows them. A tracker that groups by itself —
Sentry, Bugsnag, Flare, Rollbar, Honeybadger, Datadog Error Tracking — answers with the bug and
its count: both are kept as they are, under the code the tracker gives (SHOP-1A,
#RB57, #FL9001).
What
How it is read
One bug
The rows that share the exception, the file, and the line. OPEN and
READ in Boogle are both open: seen is not fixed
Where
A file of the server
(/home/forge/…/releases/20260920/app/Services/CustomerImporter.php) is read as
the file of the repository it is (app/Services/CustomerImporter.php:88): the
longest end of the path that exists in the checkout. A line in vendor/ is
said to be in a package
Request
The method and the path the bug was thrown on the most, and on how many other routes.
An id, a token, or an address in the path becomes {id},
{token}, {address}
Thrown
How many times, the first, the last, and — with Boogle — how many nobody opened there
yet
Outage
What the uptime monitor of Boogle recorded (#OUT…). Listed apart: an outage
is not a bug of the code by itself
The skill
in the editor
/larapilot-error
Tracker — with the errors off, one question: which tracker records them —
Boogle, Sentry, Bugsnag, Flare, Datadog, Rollbar, Honeybadger, CloudWatch — or none. The skill
never chooses for you; your answer is saved with settings-set --errors=YES
--errors-provider=…. Ask for another tracker and it asks again.
Status — one line: tracker, project, address, whether the uptime is watched.
With a credential missing, the skill says which variable and where its value is created; with
anything else missing, the hints are printed and the skill stops.
Download — errors-list --new --kind=error --report. The bugs
nobody decided about are shown in a table, the ones thrown the most first, 15 rows at most; the
detail of all of them goes to .larapilot/docs/support/errors.md (dashboard route
/larapilot/errors/errors.md; boogle.md is kept as an alias). A bug that
came back after its fix is said first.
Scope — one question: the three thrown the most and the ones that came back,
every one, the ones you name, or none.
Confirm — bug by bug (or in batches when there are many): resolve, ignore with
a reason, or skip for now. Ignores are recorded immediately with errors-link.
Plan — errors-plan --codes=… groups the confirmed codes by domain
(application, package, outage) and by place in the code: the same exception thrown in the same
folder is one group, so one spec fixes it. You approve the groups. Outages and errors in a
package never merge with application code, and a bug the tracker gives no file for stays
alone.
Read the code first — every where in the group is opened in the
repository before the handoff. When it is in a package, the call of the application that leads
there is found.
Hand off to triage — each group goes to
/larapilot-triage in the same turn, with the block below.
/larapilot-bug then writes the fix spec, with a test that throws the same exception
before the fix.
Record — errors-link BUG12,BUG15 --spec=US-012, with every code
of the group. A decision is about the bug, so one code for each bug is enough, however many
times it was thrown.
Close in the tracker — only when the fix is released and you say so:
errors-resolve BUG12, where the tracker can be closed from Larapilot.
what triage receives
Production error
codes: #BUG12, #BUG15
class: Illuminate\Database\QueryException
message: SQLSTATE[23000]: Integrity constraint violation
where: app/Services/CustomerImporter.php:88, app/Services/CustomerImporter.php:102
request: POST /admin/customers/import
thrown: 14 times
returned: false
What a bug can be
State
Meaning
new
Open in the tracker, and nobody decided about it
in_backlog
A spec of the backlog fixes it. Not fixed yet: it stays in the list
until the tracker holds it as fixed
ignored
Left as it is, with a reason of at least a sentence, given by the user
back after the fix
Closed in the tracker with errors-resolve, then thrown again. The fix did not
hold: it is shown first, and --new lists it again
no longer open
Decided about here, and the tracker holds it no more as open
A decision is kept under what the occurrences of a bug share — the exception, the file, the line —
and not under a code. The next time the bug is thrown, under a code nobody has seen, it is known
and not handed over again.
Commands
Command
What it does
larapilot:errors-status
Setting, tracker, credentials, project, whether it can be closed from here, hints. It
asks the tracker only while the errors are on, and writes nothing
larapilot:errors-list
The open errors, one entry for each bug, with their state.
--new only the ones with no decision and the ones that came back ·
--kind=error or outage · --limit= ·
--report writes {paths.support}/errors.md
larapilot:errors-plan --codes=BUG12,BUG21
Groups the confirmed codes by domain and place in the code, one triage handoff for
each group. It only reads
larapilot:errors-link BUG12 --spec=US-012
The spec that fixes the bug that code belongs to. A code the tracker does not hold is
refused
larapilot:errors-link BUG21 --ignore --reason="…"
Leaves a bug as it is, and keeps why
larapilot:errors-link BUG21 --forget
Drops what was decided: the bug is new again. Takes the key of the bug
too, which is all that is left of one the tracker closed
larapilot:errors-resolve BUG12
Writes to the tracker. Closes the bug there: resolved in Sentry,
Flare, Rollbar, Honeybadger, and Datadog Error Tracking, fixed in Bugsnag. In Boogle every
open occurrence becomes FIXED (--status=DONE for the other word
Boogle has), with Fixed by US-012. in its history, or the line given with
--comment=. Refused for CloudWatch and the logs of Datadog, before anything
is touched
errors-status, errors-list, and errors-plan are allowed
through the MCP RunArtisanTool; the two that write — errors-link to the
ledger, errors-resolve to the tracker — are not. The names the commands had when
Boogle was the only tracker still answer: boogle-status,
boogle-errors, boogle-plan, boogle-link,
boogle-resolve.
On the dashboard
/larapilot/errors is always under Insights. With the setting off it says what Boogle
is and how to connect it, and lists under it the other trackers that can be read instead. With the
setting on it names the tracker it read and opens on what is open, in a sentence — 5 open
errors, thrown 33 times: 2 with no decision yet. 1 came back after its fix — then four
figures. For a tracker that records every throw — Boogle, CloudWatch, the logs of Datadog — the
errors are then drawn day by day over the last two weeks, with the same numbers
as a table. Every bug is a row: how many times it was thrown, the exception and what it said, its
codes in the tracker, the file and the line, how long ago it last happened, and the decision. A
row opens on the whole message, the route, the first and the last time, and what to do next. Bugs
filter by decision and by text. What was read is kept for five minutes
(LARAPILOT_ERRORS_CACHE); Read again asks the tracker anew, and
Download report (.md) saves the same document the skill writes, named after the
tracker and the day. When the tracker cannot be read the page says what to check, and the rest of
the dashboard is untouched.
What is kept, and where
What
Where
Committed
What reads the tracker: address, token, keys
.env
Never
Decisions: exception, file and line, codes, spec, reason
.larapilot/boogle.yaml
Yes — the team shares one set of decisions
The report: every open bug with its decision
.larapilot/docs/support/errors.md
Your call: it quotes what the application said when it failed
The user, the query string, the payload of a request
The tracker, and nowhere else
—
The key and the token of the project, which Boogle sends with the list of projects
Compared with BOOGLE_PROJECT_KEY in memory, and dropped
—
Personal data stays in the tracker. An exception is recorded
with the user who met it and the request that caused it. None of it is needed to fix a bug, so
none of it is read: not into a file, not into the report, not into the cache, not into the chat.
An address or a long secret quoted in a message is masked, whichever tracker it comes from. The
token of Boogle reads every project of that Boogle, because Boogle gives tokens
to users and not to projects: like every credential here, it lives in .env and in the
secrets of the CI.
SBOM, CVE & Checkpoint
Two local views of security next to Aikido: the SBOM of every package the
project ships, checked against OSV.dev for
known vulnerabilities, and the last scan of
Checkpoint.
The SBOM
larapilot:sbom reads the lockfiles — composer.lock, the JavaScript lockfile of the
repository (npm, pnpm, Yarn, Bun), and the frontend companion's, its own or the workspace's in a monorepo —
and lists each package with its version, direct or transitive, production or development, license, and
package URL. Nothing is installed or downloaded. --write=both saves sbom.md and
sbom.cdx.json (CycloneDX 1.5) under paths.security.
fernway.test/larapilot/sbom
The 148 Composer packages of Fernway's lock file with their licenses, checked against OSV.dev: nothing open.
Only the name and the version of each package leave the machine. No account, no key.
Severity is the advisory's word, else its CVSS 3 score. Each vulnerable package comes with the version
that fixes all its advisories and the command that moves to it — an update when its constraint already
allows the fix.
--gate exits 1 on an open advisory at --fail-on (default high) or
above; /larapilot-ship runs it. A waived advisory does not count; one in the backlog does until
the update lands. Decisions and the trend live in .larapilot/vendor-audit.yaml: commit it.
/larapilot-vendor-check runs it with you, package by package.
Checkpoint on the dashboard
Security has two tabs: Aikido and Checkpoint. The second
shows the last scan — verdict, checks by area (dependencies, configuration, code), each finding with its
suppression hash, the trend — with Run the scan and a Markdown report.
php artisan larapilot:checkpoint-scan runs checkpoint:scan --json
(--only=, --skip=, --report, --gate) and keeps the result
in .larapilot/cache/checkpoint/, out of git, because the details can quote code. With
security_scan=YES, review and ship run it and stop on a failed check.
External frontend repo
Laravel is the only Larapilot cockpit. When topology is
API + external frontend, the UI lives in another repository — one app, or a monorepo shared
with other products — in Angular, React / Next.js, Vue / Nuxt, or Svelte / SvelteKit. Larapilot finds the
projects that are this product's, reads the rules the frontend team wrote for agents, measures how its code
is written, and implements UI there via repo: frontend tasks — or hands the work to that team.
The FE repo holds application code only — no mirrored PRD.
Topology choices
Topology
UI location
Split repo?
Laravel-coupled
Blade / Livewire / Inertia in Laravel
No
SPA-in-Laravel
Vite SPA inside Laravel
No
API + external frontend
Separate repository; Laravel is API (+ optional admin)
The absolute FE directory is stored in LARAPILOT_FRONTEND_REPO_PATH inside
.env — never in config.yaml, whatever else frontend-set changes (a
path an older version left in the YAML moves to .env). The stack, the target
projects, and the mode (driven or handoff) live in
config.yaml → frontend. The context envelope carries them as
data.frontend.
What the scan reads
Part
What it gives the agent
Workspace
Nx (the graph of the nx the workspace installed, cached until a manifest or the commit
moves; read from the files otherwise, with the targets Nx plugins infer), Angular CLI, pnpm / yarn /
npm / bun workspaces, Turborepo, Lerna, Rush, or one app; the package manager of the lockfile; the
Node version. Nothing is downloaded or installed.
Target projects
The ones this product owns, named by the user once. Each with its stack and installed version,
targets, tags, selector prefix, and the projects it depends on.
Write scope
Libraries only the targets use are owned; one another application also uses is
shared — it changes only when a task names it, and affected runs after.
Agent rules
AGENTS.md at any depth, CLAUDE.md with its @imports,
GEMINI.md, Cursor (.cursorrules, .cursor/rules/*.mdc with
globs / alwaysApply / description), GitHub Copilot
(copilot-instructions.md, *.instructions.md with applyTo),
Windsurf, Cline, Junie, Kiro, Amazon Q, Roo, JetBrains AI — each with the folder it governs and how it
applies; a symlinked CLAUDE.md is read once.
Observed conventions
Counted on the code: standalone or NgModule components, @if or *ngIf,
signal inputs or decorators, inject(), OnPush, zoneless; <script setup>
and Pinia store style; Svelte runes; App Router files; test naming, styles, file names; generator
defaults. Recent files of each kind become the models for new ones.
Commands
nx run portal:test, nx affected -t lint test build,
ng test --watch=false, turbo run --filter, pnpm --filter, … —
non-interactive, from the frontend root. Generators: the team's own first.
API client
orval, openapi-generator, ng-openapi-gen, hey-api, openapi-typescript, kubb, RTK Query codegen —
with their input and output and the command that regenerates them — or the hand-written calls.
An app in its own repository, built inside a monorepo
Some teams keep each app in a repository of its own and build it inside the monorepo they share: the app
holds a project.json and no nx.json, and its tsconfig extends the
monorepo's. Cloned at its place inside the monorepo, the scan finds the monorepo among its parent folders;
checked out somewhere else, larapilot:frontend-set --workspace=/absolute/path links it
(LARAPILOT_FRONTEND_WORKSPACE_PATH in .env). Every path of the scan is then
relative to root, the monorepo; commands run in run_in; commits go to the app's
own repository (git_root), where the monorepo's affected cannot see them, so the
scan leaves it out. Out of reach, run_in is null and the scan says where the app
expects to sit. An Angular CLI app cloned inside a monorepo is a workspace of its own, but the agent rules of
the monorepo around it still apply: they come under rules.inherited.
Checks that run, and commits that look like the team's
Karma runs headless — the launcher the karma config defines, or ChromeHeadless — with
--watch=false. A target the installed CLI can no longer run is left out of the commands and
named in unavailable: the TSLint builder left the Angular CLI in v13, Protractor in v19, and
once the packages are installed their own builder catalog decides. A project with no spec file, or whose
generators skip them, is a question for the user, recorded in the decision journal. The scan reads the
commit history (git.commits): Conventional Commits or not, types, scopes, the language of the
subjects, commitlint and hooks; a task's commit follows it with US-012 TASK-03 in the subject.
Vendored packages — built third-party code copied into the repository — are listed under
write_scope.vendored and never edited.
The frontend team's rules win on code
The editor loads the agent rules of the workspace it opened — the Laravel one — never those of the
frontend repository. So implement reads every rule that governs the target projects before its first
frontend edit, and asks frontend-rules --file=… before each task which rules govern the files
it writes: a Cursor rule on *.component.ts, a Copilot rule on **/*.spec.ts, an
AGENTS.md deep in a feature folder. Inside the frontend repository those rules decide
structure, naming, state, tests, commits; Larapilot decides only the workflow. A deeper folder wins; two
rules that disagree go to the user. Review checks the diff against the same rules.
Playbooks
For what the rules and the code leave open, one playbook per stack — Angular, React (Next.js, React
Router, React Native), Vue (Nuxt), Svelte (SvelteKit) — says what each major version adds, so an Angular 15
workspace never receives Angular 20 code.
CLI commands (all from Laravel)
Command
When
larapilot:frontend-set --path=… [--stack=Angular]
Once — writes LARAPILOT_FRONTEND_REPO_PATH to .env
A monorepo: the projects of this product (checked against the workspace;
--clear-projects forgets them)
larapilot:frontend-set --workspace=…
An app in its own repository whose monorepo is not among its parent folders —
LARAPILOT_FRONTEND_WORKSPACE_PATH in .env (--clear-workspace)
larapilot:frontend-set --mode=driven|handoff
Who writes the frontend: Larapilot, or the frontend team from a brief
larapilot:frontend-scan
Once per spec, before plan and implement — --project=, --full,
--no-cli, --fresh
larapilot:frontend-rules --file=…
Before writing — the rules that govern those files
larapilot:frontend-brief US-012
Handoff — the story, the frontend tasks, the API operations they call, and the mockups, in
.larapilot/docs/frontend-briefs/ (--stdout returns it instead)
Delivery
Laravel — PRD, backlog, plans, mockups, workflow. All /larapilot-*
skills.
Frontend repository — application code from larapilot-implement tasks with
repo: frontend (and project: in a monorepo), committed with its hooks on and
US-012 TASK-03 in the subject. task-done finds that commit in the frontend
repository. No Larapilot workflow there.
Handoff — in handoff mode implement runs the backend tasks and writes the
brief; the frontend team builds in its own session, where its rules load by themselves, and names its
commits.
Mockups — always in Laravel .larapilot/mockups/; Joe implements them in the
FE repository.
frontend-scan → in a monorepo, name the projects → frontend-set --project=…
/larapilot-spec → plan → implement (BE + FE tasks from Laravel, or the brief in handoff)
PRD edits happen on Laravel only. The FE repo never holds a mirrored PRD.
Design systems
Five packaged visual references ship in .larapilot/design-systems/ on
larapilot:install and refresh on larapilot:update. /larapilot-design
picks the folder that matches the PRD stack choice — Elise and Joe enforce token and component consistency
through implement and review.
System
Path
When to use
Contents
Filament
design-systems/filament/
Admin/control panel mockups when the PRD records Filament
Open each folder's html/index.html locally as a visual catalog — or browse every
project mockup in the dashboard Design gallery
(/larapilot/design). Skills resolve paths via
php artisan larapilot:config-show → paths.design_systems.
Sign-in screens: the fields are drawn, never built
A mockup is a picture of the screen, not a working form. Password managers
(1Password, Bitwarden, iCloud Keychain, the browser's own) scan every <input> they
find, and the Design gallery renders the same screen several times at once — the card, the viewer, the
style comparison. One real sign-in field makes them pop up, fill in, and offer to save on every
preview. So /larapilot-design follows one hard rule, and the five packaged systems ship a
login.html that shows how:
No <input>, <textarea>, or <form>
for credentials — username, login email, password, confirm password, PIN, one-time code —
on login, register, reset, two-factor, change-password, and create-user screens.
No workaround counts. A masked type="text",
autocomplete="off", data-1p-ignore, or a renamed label are all ignored by
password managers, which guess from the layout.
The field is drawn: a <div> styled like the input of the design
system, holding sample text (jane@example.com, ••••••••••), with
role="img" and an aria-label. Its label is a <span>.
The button is a link to the next screen
(<a role="button" href="dashboard.html">), so the flow stays clickable in the
viewer. Error, empty, and focus states are separate screens.
Every other control stays real: search, name, amount, select, the Remember me checkbox.
Filament and Starter Kit carry the classes in tokens.css — .mock-label,
.mock-field, .mock-field--secret, .mock-field--empty. The real
Fortify or Filament fields are built by Alex at implementation time.
Custom design systems & templates
You can add your own design system or UI template under
.larapilot/design-systems/{your-name}/ and use it for mockups
(/larapilot-design) and implementation — not only the five packaged references.
Drop tokens, component notes, HTML starters, and brand assets in that folder (same shape as the packaged
ones helps: README.md, tokens.css, components.md, optional
html/).
Record the choice in the PRD (or tell the agent during inception/design) so Elise, Joe, and Alex treat
it
as the source of truth for mockups and shipped UI.
larapilot:update refreshes only the packaged systems — your custom folder is never
overwritten.
Code quality
Every Larapilot project stays compatible with Larastan (PHPStan level 5+) and Laravel Pint. The gate is
installed with the package and is not optional.
Andrew + Jack own the gate — Pint for style, Larastan for static analysis.
Robert flags regressions at review when quality was skipped.
Artisan CLI
Skills save everything through php artisan larapilot:* commands. You do not
need to learn them: they are how the agent writes files, checks them, and moves a story from one status
to the next. Run one by hand for debugging, scripting, or CI — for example
php artisan larapilot:doctor --human to check the install.
Skills never invent persistence — they always go through these commands and parse the envelope. Errors
carry a stable code and a matching exit status:
Error code
Exit
Typical cause
E_INVALID_INPUT
2
Bad flag value, invalid payload, a custom skill name that is not kebab-case
E_CONNECTOR
3
The persistence connector failed
E_PRECONDITION · E_NOT_FOUND
4
A guarded transition (implement before plan, release command with release_mode=NO),
an unknown spec or file
anything else (e.g. E_QUALITY)
1
Pint or Larastan findings — carried on error.details
Ask for the slice you need
Envelopes stay small on purpose, so an agent's context does not fill up with data it will not use:
Terminal
php artisan larapilot:context implement # settings, paths, project facts, files to read
php artisan larapilot:context review --session=k3f9x2 # same conversation: only what is new
php artisan larapilot:context plan --fresh # after a compaction: everything again
php artisan larapilot:spec-list # the backlog without the bodies (--full for all)
php artisan larapilot:prd-show # outline of the PRD: sections, ids, MoSCoW
php artisan larapilot:prd-show --ids=FR-004,J-001 --section="Technical Architecture"
php artisan larapilot:config-show --only=tracker # a slice context does not carry: tracker, backstage, workflow, personas
php artisan larapilot:spec-show US-004 --fields=id,title,status,dependencies
php artisan larapilot:spec-show US-004 --task=TASK-02 # one task only
php artisan larapilot:spec-next --status=PLANNED --fields=id,title
php artisan larapilot:quality -v # raw Pint/Larastan output inside the JSON
php artisan larapilot:doctor --human # a table instead of JSON (also metrics --human)
larapilot:hook-list (--event=) ·
larapilot:hook-run {event} (--phase=, --spec=,
--task=, --release=, --dry-run) ·
--skill-hooks-done= on every command that fires an event
The skills that move a spec, write the PRD, or ship, when hooks=YES — see
Workflow hooks
RunArtisanTool checks the parameters too. Each command takes through MCP only the
parameters that read, and an option that writes a file is refused before the command runs:
quality --fix, backstage-export --write / --force /
--catalog= / --mkdocs= / --file=,
usage-report --output=, aikido-issues --report,
aikido-repos --use= / --forget, errors-list --report,
upgrade-check --report, sbom --write=, vendor-audit --report. Run directly with Artisan, the commands take every option as
before. All four tools are annotated as read-only (readOnlyHint), and the schema of
command lists the commands that are allowed.
Dashboard
The workspace in .larapilot/, as pages your own Laravel app serves at
/larapilot. It is where you read what the skills write: review backlog progress, read
the PRD, browse the Design gallery, inspect settings/choices, Lucille usage, and Economics
quotes — then drill into spec detail between skill sessions.
With comments=YES you can also post internal feedback from the spec page
(dev/staging only), the File manager uploads and organizes the material the skills
read, Database shows the tables of the application and what is in them, and
Logs reads what it logged — workflow state still changes only via skills/CLI.
Served by your Laravel app
Nothing else to run. The pages are routes of your application, in its
web middleware group. Herd, Valet, Sail, php artisan serve: whatever already
serves the app serves the dashboard.
Nothing to build. Blade views that carry their own styles: no Node, no
npm run build, nothing added to the asset pipeline of your app.
No account. It reads .larapilot/, the Git history of the repository,
and the database in .env: nothing to sign up for, nothing hosted somewhere else.
Current on every reload. Each page reads the files when it loads, so what a skill
wrote a minute ago is there. No sync, no export.
Off where it should be. It answers only in the environments you allow, never in
production, and dashboard_auth puts a password
in front of it on a shared staging server.
Activation
The dashboard is on by default after php artisan larapilot:install. Routes
register automatically when the package boots. All of the following must be true:
LARAPILOT_ENABLED=true (default) — master switch in config/larapilot.php
Environment is in the allowlist: local, development, testing, or
staging
Start your Laravel app the way you already do and open /larapilot on it:
open the dashboard
php artisan serve # → http://127.0.0.1:8000/larapilot
# Herd or Valet # → http://your-app.test/larapilot
# Sail # → http://localhost/larapilot
If routes 404, check APP_ENV and the flags above. The prefix below moves it
elsewhere.
config/larapilot.php — dashboard_route
'dashboard_route' => [
'enabled' => env('LARAPILOT_DASHBOARD_ROUTE', true),
'prefix' => 'larapilot', // change to customize URL prefix
'middleware' => ['web'],
'environments' => ['local', 'development', 'testing', 'staging'],
// Optional HTTP Basic Auth on the UI — enforced only when the
// `dashboard_auth` project setting is ON. Never gates the JSON API or MCP.
'auth' => [
'file' => base_path('.larapilot/auth.yaml'), // hashed credentials, git-ignored
'realm' => env('LARAPILOT_DASHBOARD_AUTH_REALM', 'Larapilot'),
'max_attempts' => (int) env('LARAPILOT_DASHBOARD_AUTH_MAX_ATTEMPTS', 30),
],
],
Authentication opt-in
The dashboard UI is open by default in the allowed environments. Turn on the
dashboard_auth project setting to require
HTTP Basic Auth:
enable the dashboard gate
php artisan larapilot:dashboard-user add andrea # prompts for a password, stores only the hash
php artisan larapilot:settings-set --dashboard-auth=YES
Credentials are argon2id/bcrypt hashes in .larapilot/auth.yaml (no database, no
User model; added to .gitignore automatically). Failed sign-ins are rate-limited
per IP. This gate is UI-only — it never applies to /larapilot/api/* (use
LARAPILOT_API_TOKEN) or the MCP server. Full reference:
Project settings → Dashboard auth.
Pages
Tab
URL
What you see
Board
/larapilot
Kanban backlog — specs grouped by workflow status (TODO →
DONE), the status as it stands in one Markdown file
(/larapilot/board.md), and the epics as an outline of stories and tasks
(/larapilot/epics.md)
PRD
/larapilot/prd
Rendered PRD.md with a section table of contents, a search that
looks in the PRD and nowhere else, the decision journal, and two downloads: the whole PRD
(/larapilot/prd/prd.md) and the functional analysis summary
(/larapilot/prd/functional-summary.md)
Inception
/larapilot/inception
Discovery choices snapshot from .larapilot/choices.yaml
Plan
/larapilot/plan
The delivery forecast: a dependency-aware Gantt with open work queued from today, by epic or in delivery order (time axis, today marker, task filters), milestones, and schedule criticality. Download /larapilot/plan/plan.md: epics, every story with status, priority, points, release, blockers, and forecast window, the tasks of each planned story, milestones, and the delivery order
Design
/larapilot/design
Every screen first, as a card; a click opens that mockup as a site you browse, with
All screens to come back. Prev/next through every flow,
style variants under mockups/{spec}/styles/{slug}/, each
listed on its own, and Use this style on the open screen, download
/larapilot/design/package.zip (HTML + assets)
Settings
/larapilot/settings
Current settings.* with allowed options and per-option explanations
Skills
/larapilot/skills
Every skill the agents of the project can run, whoever brought it: the project,
Larapilot, another package, Laravel Boost, the folder of an agent. Click one to
read it. Under them, the files the agents are told from, one part for
each author
File manager
/larapilot/files
The five material folders, each with what it is for: browse the folder tree, preview and
download files, read a PDF in the page, upload files or a whole folder with its structure,
rename, delete. A sixth folder, Project, shows the application itself,
read only
Database
/larapilot/database
The tables and views of the database in .env, whatever the driver — MySQL,
MariaDB, PostgreSQL, SQLite, SQL Server. Open one for its rows, a page at a time, with sort,
search, and a foreign key that leads to the row it points at; or for its structure: columns,
indexes, foreign keys. Diagram draws the tables and the foreign keys
between them; Migrations lists the ones that ran and the ones still to
run. Download SQL dumps the whole database; Other formats gives its
structure alone, Laravel migrations, and seeders with its rows.
Read only; passwords and tokens are never shown
Logs
/larapilot/logs
The log files of the application read as entries, the newest first: by level, by period,
searched, or with the repeats counted — one row for each thing logged. An exception shows
where it was thrown, and the frames of your own code apart from the framework's.
Download log for the file. Read only; passwords, tokens, and
keys are shown as [REDACTED]
Laravel
/larapilot/laravel
How the framework is set up in the application and what it is doing, in five tabs.
Overview: the drivers in use and what is cached. Schedule:
the scheduled tasks, the next one due first. Queue: the jobs that wait, are
delayed, or are held by a worker, and the ones that failed. Mail: the mail
the application sent, as it left. Dumps: what dump() and
dd() printed, with the line that dumped it. Nothing of the application is
changed
Git
/larapilot/git
Last-12-month contribution heatmap, every branch measured against the branch it is heading
for, and the history drawn as a graph — one lane per line of work, each commit on the branch
it was made on. Filterable by developer
Usage
/larapilot/usage
Lucille token and hour ledger by category, plus download
/larapilot/usage/report.md. Schedule and Gantt are on Plan
Security
/larapilot/security
Two tabs. Aikido: what Aikido found in the repository, with what was decided
about each finding and the verdict of the ship gate; with aikido off, two lines on what
Aikido is and how to connect it. Download /larapilot/security/aikido.md.
Checkpoint (/larapilot/security/checkpoint): the last scan of
Checkpoint — verdict, checks by area, findings with their suppression hash, trend — with
Run the scan and /larapilot/security/checkpoint.md
SBOM
/larapilot/sbom
Every package of the lockfiles — Composer, the JavaScript of the repository, the frontend
companion — with version, scope, license (copyleft flagged), abandoned packages, and the
vulnerabilities of the last OSV.dev check grouped by package, with the fix and the decision.
Check vulnerabilities runs it. Downloads: sbom.md, sbom.cdx.json
(CycloneDX 1.5), vendor-audit.md
Errors
/larapilot/errors
What the running application threw, as the tracker of the project recorded it: one row
for each bug, how many times it was thrown — day by day when the tracker records every
throw — and what was decided. Always listed; with errors off, what Boogle is,
how to connect it, and the other trackers that can be read instead. Download
/larapilot/errors/errors.md (boogle.md still answers)
Economics
/larapilot/economics
What the project costs, what the client pays, and what is left (when account is
FREELANCE or COMPANY): the short answer, every sum written as a receipt, where the money goes,
and — for a subscription — when it comes back. Downloads: client quote
/larapilot/economics/quote.md, internal report
/larapilot/economics/report.md
API docs
/larapilot/api/docs
Interactive OpenAPI browser for the workflow JSON API
Docs
/larapilot/docs
Delivery loop, packaged Boost skills, and persona roster
About
/larapilot/about
What the project runs on: Laravel, PHP, the database server, and Node, each with its support
window as a bar (bug fixes, security fixes, today) and an alert near or past its end; the project,
extensions, connections, drivers, the packages that shape an upgrade, frontend, CI and deploy, and
every file that pins a version
Pages are reached from a sidebar grouped as Workflow, Workspace, Insights, and Reference; on a phone the
sidebar is a drawer behind the menu button. The dashboard follows the system theme, and the switch at the
foot of the sidebar pins light or dark for that browser.
File manager
/larapilot/files manages what you hand the skills before they start. It opens on five
folders you can change, and on the project itself, which you can only read:
fernway.test/larapilot/files
Brand, client materials, design systems, legacy snapshots, and custom skills — and the project itself, read-only.
Folder
What it is for
Read by
.larapilot/brand/
Logo, palette, typography, and the brand guide
Design and mockups
.larapilot/client-materials/
Briefs, analyses, and documents supplied by the client
Inception and specs
.larapilot/design-systems/
Visual references and tokens the mockups are built on
Design and mockups
.larapilot/legacy/
Snapshots of the old system to port or migrate
Adopt, inception, and parity checks
.larapilot/skills/
Your custom skills, one folder per slash command
Boost slash commands
./ — Projectread only
The Laravel application itself: code, config, routes, and tests
Inside a folder there is a folder tree on the left and the contents on the right. Markdown, text, and
images are previewed in the page; any file can be downloaded. Upload folder, or a folder
dropped on the page, keeps its structure: every file lands at the path it had inside the folder. A file
that already exists is kept unless Replace existing files is on, and whatever was skipped
is listed with the reason. The five folders themselves cannot be renamed or deleted, and the packaged
design systems are marked, because larapilot:update rewrites them.
A PDF is read in the page. One page or two side by side (the first stands alone,
like a cover), zoom with the buttons, +−0 or
Ctrl/Cmd + wheel, page jump, arrow keys, drag to move around an enlarged page,
and full screen with F. The reader is PDF.js 3.11 from cdnjs, checked against its hash;
where it cannot load — offline, or scripts off — the file opens in its own tab as before.
Project is read only. The routes that upload, create, rename, and delete do not
know the folder, and the service refuses a write to it whoever asks. The code is changed in your
editor and by the skills, never from a web page: an upload into public/ on a shared
host would be code anyone signed in could run.
Folders that start with a dot are left out of Project — .git,
.larapilot, .github, .idea, at any depth — and so are the
caches of the framework, bootstrap/cache/ and storage/framework/: a cached
configuration holds every value of .env. They are not
listed, not counted, and answer 404 by address, a link that resolves into one
included. Files that start with a dot are shown: .env.example,
.gitignore, .editorconfig. vendor/ and
node_modules/ are listed and can be opened; they are left out of the count and the
tree unfolds only along the folder you are in.
Credentials show their keys, never their values. In every folder, a file that
holds credentials is shown with each value replaced by ***************** — on screen
and in the download, so the real bytes never leave the server. That covers .env and
.env.* (a template such as .env.example is shown as it is),
auth.json, .npmrc, .yarnrc, .netrc,
.pgpass. Comments stay readable; a commented-out value and the later lines of a value
written over several lines are masked too. A key or a certificate (.pem,
.key, .p12, .pfx, id_rsa, …) is one line of
asterisks. A database (.sqlite, .db) is listed, and neither shown nor
downloaded. A .log file is shown and downloaded with the passwords, tokens, and keys
it quotes replaced by [REDACTED], as on the Logs page, which reads it whole, by
entries, where the file manager shows its first 256 KB.
Local by default. The file manager is open in local,
development, and testing. On any other environment
(staging) it is served only when
dashboard_auth is YES, and answers
404 otherwise: client documents and legacy snapshots stay behind a sign-in.
Upload size. One file is limited by
LARAPILOT_FILE_MANAGER_MAX_UPLOAD_KB (default 51200, 50 MB) and by PHP's
upload_max_filesize and post_max_size, whichever is lower. The page shows the
limit in force.
Nothing leaves the folder. A path outside the six folders returns 404,
a symlink is listed and never followed, and an uploaded .html, .js, or
.svg is never run in the dashboard.
Custom skills stay in step. A change under .larapilot/skills/{name}/
refreshes that skill's copies in .ai/skills/ and the agent folders; deleting the skill
removes the copies that still match it.
LARAPILOT_FILE_MANAGER=false removes the page.
Database
/larapilot/database shows the application's own database: the connection named by
DB_CONNECTION, with the DB_* values in .env. Everything goes
through Laravel's schema and query builders, so the page is the same on MySQL, MariaDB, PostgreSQL,
SQLite, and SQL Server, and on any Laravel version Larapilot supports. It reads, and never writes.
fernway.test/larapilot/database/bookings
The rows of bookings, filtered and sorted on the server.
fernway.test/larapilot/database?view=migrations
Migrations: two still to run, eight ran over seven batches.
The list opens on every table and view, with its size where the driver reports
one, and the driver, the database, and the host on top — never the password. On MySQL and MariaDB
only the database in .env is listed, not every database the user can see; on
PostgreSQL every schema is, and a table outside public is named
schema.table. With a prefix on the connection, tables are named without
it.
Rows: 50 to a page (LARAPILOT_DATABASE_VIEWER_PER_PAGE), ordered by the
primary key. A click on a column sorts by it; the search looks in the text columns, without regard
to case. A value in a foreign key column is a link to the row it points at. A click on a row opens
it whole: long text in full, JSON indented, Copy as JSON, and
Copy as SQL INSERT — the row as one INSERT statement of the driver,
its columns named and its values written as the dump writes them, ready to run on a database with
the same table; a hidden column goes out without its value, and the statement says which. A binary
value is shown as hex with its size, NULL as NULL.
Structure: the columns with type, null, default, primary key, auto increment,
and comment; the indexes; the foreign keys with what happens on update and on delete; and the
create statement — the CREATE TABLE, or CREATE VIEW,
that builds it, with its indexes and keys, in the SQL of the driver, with a button to copy it. It
is the statement the dump writes for that table.
Diagram is the second view of the list
(/larapilot/database?view=diagram): a box for each table with its columns —
PK, FK, the type — and a line for each foreign key, from the column to
the one it references, with an arrow on that end. A table stands to the right of the tables it
points at, so the ones everything depends on are on the left; the tables and views no foreign key
touches are in rows below. Keys only leaves the keys in each box and counts the
other columns. A click on a table keeps it, its lines, and the tables at their other end lit, and
dims the rest; its name opens it. −, +, and Fit zoom,
as Ctrl or ⌘ with the wheel does; a drag moves around. The diagram is drawn on the server as
plain SVG — no library, nothing fetched from outside — so it reads without scripts too, and it
shows the structure alone, never a row. It is drawn for up to 300 tables and views.
Download PDF saves the diagram as it is shown — all columns or keys only — as
/larapilot/database-diagram.pdf: one page as large as the drawing, in vectors, so
it stays sharp at any zoom and prints on whatever paper it is scaled to, with the database, the
driver, the counts, and the day on top. The file is written by Larapilot itself, with the two
Courier fonts every PDF reader has: no PDF library is installed, and a name with letters outside
Western European shows a ? for each of them.
Migrations is the third view
(/larapilot/database?view=migrations): which migrations ran and which are still to
run, as php artisan migrate:status tells it — the files of the application and of
its packages against the migrations table of the connection. The pending ones come
first, then the ones that ran with their batch, the newest first, each with the day it was
written; a migration that ran from a file that is no longer there is marked
Ran · file gone. Before the first migrate the table is not there
and every file is pending — the page says so — and it is offered on a database with no table
yet. A field narrows the list by name, state, or package. Nothing is run from the dashboard:
the page names the command, php artisan migrate, and the batch it would be.
Credentials are never shown. A column named like a password, a token, or a secret
— password, remember_token, *_token, *secret*,
two_factor_recovery_codes, *api_key*, *private_key* — is
shown as *****************, and is never searched, sorted, or filtered on, so its
value cannot be guessed one query at a time. database_viewer.masked_columns in
config/larapilot.php adds patterns of your own.
Download SQL writes the whole database as one .sql file, in the
dialect of the driver it came from, to restore with mysql, psql -f,
sqlite3, or sqlcmd: the structure, every row, then what has to come after
them. MySQL, MariaDB, and SQLite give their own CREATE statements — generated columns,
AUTO_INCREMENT, SQLite triggers, and the next AUTOINCREMENT number come
along, and a view loses only its DEFINER. PostgreSQL is rebuilt from its catalogs the
way pg_dump does it, every name with its schema: schemas, enum types, serial and
identity columns with their next value, generated columns; primary keys, unique and check
constraints, indexes, foreign keys, and views follow the rows. SQL Server and other drivers are
rebuilt from Laravel's schema builder. Each table and view is dropped first if it exists; triggers
outside SQLite, functions, and grants are not part of the file.
The dump is written while it downloads, a thousand rows at a time, from one
read-only snapshot: a large database neither fills the memory nor comes out half-updated. The file
opens with the driver, the database, the date, and what was left out, and ends with
-- Dump complete. — or with the error that stopped it.
Credentials stay out of the dump unless you ask: the hidden columns are written
as NULL, or as an empty string where NULL is not allowed — as
hidden-1, hidden-2, … where a unique index holds the column, so the file
still restores — and the head of the file names them. Include passwords and tokens puts them in, and is offered only in
local, development, and testing — on a shared host they never
leave the server, sign-in or not. The seeders follow the same rule, and
Copy as SQL INSERT never carries them.
Other formats, beside Download SQL, gives the database three
more ways:
SQL, structure only — the same file with no rows: the tables, their keys
and indexes, and the views, and no sequence left at the number the rows had reached.
Laravel migrations — a .zip to unpack in the root of a
project: a file in database/migrations for each table, written with the
Blueprint method that makes each column — id(), timestamps(),
softDeletes(), rememberToken() where a table carries them — its
indexes, and its foreign keys. A table comes after the ones it points at; the keys that
close a circle are in a last migration of their own, and the views in one that runs their
SQL. What Blueprint has no word for — a type of one database only, an index on an
expression — is written as the nearest thing, with a comment on the line above. The
migrations table is left out: Laravel makes it itself.
Laravel seeders — a .zip with a class in
database/seeders for each table that holds rows, and
DatabaseDataSeeder, which calls them in the order of the migrations:
php artisan db:seed --class=DatabaseDataSeeder, on tables that are there and
empty. Bytes are kept through base64, the next id is set on PostgreSQL, where the foreign
keys are let wait until every table is filled, and the archive is written while it
downloads, so a large table fills neither the memory nor the disk.
The migrations and the seeders are read from Laravel's schema builder, so they are the same from
every driver, and what one database wrote runs on another — but for a view and a generated
column, which are the SQL of the database they came from. The downloads are at
/larapilot/database-export/sql, /migrations, and
/seeders, with no extension, since a web server may keep .sql and
.zip for itself; /larapilot/database.sql still answers.
Local by default. Like the file manager, the page is open in
local, development, and testing; on any other environment it
is served only when dashboard_auth is
YES, and answers 404 otherwise.
When the database cannot be read — the server is down, a value in
.env is wrong — the page says so with the driver's own message.
LARAPILOT_DATABASE_VIEWER_CONNECTION points it at another connection of
config/database.php; LARAPILOT_DATABASE_VIEWER=false removes the page.
Logs
/larapilot/logs reads what the application wrote to storage/logs: every
.log file, the one Laravel writes to now opened first. It reads, and never writes. A
file has its address without the extension — /larapilot/logs/laravel for
laravel.log — because a web server may refuse an address that ends in
.log, or look for a file of its own there; the address with it leads to the one
without.
fernway.test/larapilot/logs?view=groups
Repeats counted: seventy entries become one row for each thing logged — seven times the same full class, twice the same Stripe key, redacted.
Entries, not lines. An entry is what Laravel wrote between one
[date] env.LEVEL: and the next, its stack trace included; the newest comes first. Each
one shows its level and message and, for an exception, the class, where it was
thrown as a file of the project — a path of the server is read as the file it is in this
checkout — the exception that caused it, and the context as indented JSON.
Your code apart from the framework's. A stack opens on the frames of the
application; the ones of the framework and the packages are one click away. A file that is in the
checkout opens in the file manager.
By level, by period, by words. A level returns itself and every one more severe
(Error and worse), and the count of each level sits above the list. The search wants every
word, in any case; quotes keep a phrase together, and a class name is found with its backslashes
as the page shows them or as the log doubles them. The period is the last hour, day, week, or
month.
Repeats counted turns the list into one row for each thing logged — the same
message with its numbers, ids, and quoted values taken out, or the same exception thrown from the
same line — with how many times and since when, the most repeated first. Every time it was
logged goes back to the entries of that one. A log where nothing repeats is counted up to
10,000 different things, and the page says so.
Any size. A file is read from its end backwards — 32 MB for a request
(LARAPILOT_LOG_VIEWER_SCAN_MB), 50 entries to a page
(LARAPILOT_LOG_VIEWER_PER_PAGE) — so the newest entries of a log of gigabytes come at
once. Older and Keep reading older go further back, and a page
stays the same however much is written after it. Of an entry longer than 16 KB the start is shown,
with its real size. A file that is not in Laravel's format, such as the output of a worker, is read
a line at a time.
Secrets are never shown. Passwords, tokens, keys, cookies, and
Authorization headers are [REDACTED] on screen, a value written as JSON
("password":"…") included, and a search never finds one. Under such a key the value is
hidden whatever it is — a string, the list a header comes as, a number — and so is the JSON of a
request body logged as text; a line the redaction cannot check is hidden whole.
Download log gives the file redacted the same way, with a line longer than 1 MB cut
at that size; Secrets as written gives it as it is, and is
offered only in local, development, and testing — on a shared
host they never leave the server, sign-in or not.
Local by default. Like the file manager, the page is open in
local, development, and testing; on any other environment it
is served only when dashboard_auth is
YES, and answers 404 otherwise. Only the .log files of the
folder are opened, three folders deep, and a symlink is never followed.
LARAPILOT_LOG_VIEWER_PATH reads another folder — absolute, or from the root of the
project; LARAPILOT_LOG_VIEWER=false removes the page.
For the skills, php artisan larapilot:logs reads the same files —
see Diagnostics and logs.
Laravel
/larapilot/laravel shows how the framework is set up in the application and what it is
doing now. Five tabs; nothing of the application is changed from any of them — no cache is cleared,
no job retried, no mail sent again.
fernway.test/larapilot/laravel
Overview: the drivers and caches of the running app.
fernway.test/larapilot/laravel/queue
Queue: jobs waiting, delayed, and failed on the database queue.
fernway.test/larapilot/laravel/schedule
Schedule: the tasks as schedule:list reads them.
fernway.test/larapilot/laravel/dumps
Dumps: what dump() printed in a controller and in a queued job.
Overview. Five numbers that lead to the other tabs — scheduled tasks, jobs
waiting, failed jobs, mail kept, dumps kept — then two panels. Drivers: for the
database, the cache, the session, the queue, the mail, the filesystem, broadcasting, logging, and
hashing (Scout and Octane when installed), the store, connection, or mailer in use, its driver,
and what says where it points — host and port, bucket, table, prefix — never a password or a key.
A driver that keeps nothing is said so: with sync a job runs inside the request, with
log a mail reaches nobody. Caches: whether the configuration, the
routes, and the events are cached, how many views are compiled, and the cache of the application
— its store, whether it answers, and how much it holds where that can be counted
(file, database) — each with the artisan command that builds it and the
one that empties it. On a developer's machine a cached configuration carries a warning: a change
to .env is not read until it is cleared.
Schedule. The tasks of the scheduler as
php artisan schedule:list prints them, read from routes/console.php,
withSchedule(), or the console kernel: the command, the description, the cron
expression, the next run, and its options — no overlap, one server, background, in maintenance,
the environments it is limited to. A task that does not run in this environment is greyed. The
page cannot tell whether the cron of the server calls schedule:run.
Queue. On the default connection — or another one of
config/queue.php, a click away — how many jobs wait, are delayed, or are held by a
worker, queue by queue, and the first 50 jobs in the order a worker takes them: class, queue,
state, attempts, since when. The jobs are listed for the database and
redis drivers — on Redis the queues are the one of the connection, the ones Horizon
is told to work, and any other that holds a job; SQS, Beanstalkd, and the others give the numbers
only. Failed jobs lists the last 50 with the first line of the exception, secrets
redacted, and the queue:retry command for each; open batches are counted.
Mail. Every mail the application sends is kept and listed, the newest first:
subject, recipients, the mailable or notification that built it, the request or command it was
sent during. A mail opens on its headers, the names and sizes of its attachments — not the files
— and the message: the HTML inside a frame that runs no script, where a link opens in a new tab,
and the plain text. It listens to what Laravel says it sent, so it works with every mailer,
log and array included, and changes nothing of the mail or of where it
goes.
Dumps. What dump() and dd() printed, the newest
first: the value as text, the file and line that dumped it — the Blade template, for a dump in a
view — and the request or command it happened in. A dump still shows where it always did; here it
stays to be read, also when it came from a job, an API call, or a response nobody saw. While
Laravel Herd is watching the dumps it takes them all, and the page says so.
Where they are kept. The last 100 mails and the last 100 dumps
(LARAPILOT_LARAVEL_VIEWER_KEEP) sit in storage/larapilot/, a folder that
ignores itself in git (LARAPILOT_LARAVEL_VIEWER_PATH moves it). Forget them
all empties a list. They are kept on a developer's own machine only —
local and development, and not while the tests of the project run: a
mail carries reset links and personal data, a dump whatever the code was holding.
LARAPILOT_LARAVEL_VIEWER_MAIL=true or
LARAPILOT_LARAVEL_VIEWER_DUMPS=true keeps them wherever the page is served;
false never.
Local by default. Like the file manager, the page is open in
local, development, and testing; on any other environment it
is served only when dashboard_auth is
YES, and answers 404 otherwise.
LARAPILOT_LARAVEL_VIEWER=false removes the page.
Board
Summary metrics at the top: total specs, done count, completion rate, and WIP. A search field and
priority, epic, and status filters narrow the columns; the counts follow whatever is still visible.
Each workflow column lists spec cards — code, title, epic, story points, priority, subtask progress bar,
mockup indicator when HTML exists, pill badges for comment and blocking counts when feedback exists, and
a merge-request link when the story is DONE. That link sits beside
the card target, so the board does not leave a blank card next to a merged story.
Download status (.md) saves the board as it stands — a summary, specs and story points
by status, then every spec with its priority, points, epic, task progress, and merge commit.
Download epics (.md) saves the same stories as an outline: the project as the title,
each epic with the story points of its stories, each user story with its own, and under a planned story
its tasks — a task is estimated in hours, so that is what it carries. Epics and stories come in the
order of their codes, the stories with no epic last. With a filter on, either file holds what is on
screen and says which filter produced it; an epic then counts the points of the stories that are left.
fernway.test/larapilot
Fernway's board: each card with points, priority, task progress, mockup, comments, and the merge that closed it.
PRD
The living product document from /larapilot-inception (and later
/larapilot-feature, /larapilot-bug, or /larapilot-prd updates). Headings become anchor links in the
sidebar; the body is rendered Markdown — the same file skills write to .larapilot/docs/PRD.md.
The search field finds a requirement, a persona, or a word as you type, in the PRD
and nowhere else: the index is built in the browser from the headings of the document and the text
under each, so the menu and the decision journal are never found. / or
Ctrl/Cmd+K focuses it, the arrows and Enter move and open,
a result lands on the first match of its section with every match highlighted, accents are ignored
(perche finds perché), and ?q= in the address opens the page on a
query. Download PRD (.md) saves the whole document exactly as it is on disk.
Download functional summary produces one Markdown file in the PRD's language for someone
who will never read the full PRD: the one-sentence pitch, the personas, what is in and out of this
version, and every requirement as a numbered point — required first, then what matters, then what can
wait. The wording of each point stays the PRD's; only the frame is translated.
Below the PRD, the decision journal from .larapilot/decisions.yaml is grouped
(project / discovery vs per user story) with a timeline — see
Decision journal.
fernway.test/larapilot/prd
The PRD of Fernway, its sections on the left and a search that looks in the PRD alone.
Inception
The discovery choices snapshot from .larapilot/choices.yaml — Project Kind, Delivery
Target, Business Model, operations and support, topology, data store, and the other fixed choices
inception persisted.
fernway.test/larapilot/inception
What discovery fixed: the kind of product, how it makes money, the stack, where it runs.
Plan
The schedule: epics, milestones, schedule criticality, and a dependency-aware Gantt with a time axis, a
today marker, epic grouping, and filters for assignee, status, and task detail. Lucille's schedule notes
appear when present.
fernway.test/larapilot/plan
Milestones by release — 0.1.0 shipped, so done — and the Gantt by epic, with tasks hidden.
The Gantt is a forecast in sequence. Open work is queued from today, one spec at a
time: what is in progress first, then by priority and code — the order of spec-next
— and never before the specs named on its **Blocked by:** line; a spec that blocks a
more urgent one is as urgent as it. A working day is 6
hours, Monday to Friday; a spec with no plan counts 4 hours for each story point. Inside a spec a task
waits for its dependencies and for its assignee, so only tasks given to different people overlap. Done
work sits in the past, on the days the ledger recorded for its spec. Forecast end is the
day the last open spec is done. An epic deadline is measured against the last spec of the epic, a
milestone that names a release (schedule-set --release=0.2.0) against the last spec of that
release, and any other milestone against the forecast end. A milestone that names a release is done
once that release is shipped: it is never reported overdue for a release already out.
/larapilot-schedule re-plans the order and the dates.
View switches between the specs grouped by epic and one queue in
delivery order. A plan wider than the page opens as the whole plan;
Zoom → Detail widens the days and opens on today.
Design
Open /larapilot/design for every HTML mockup produced by
/larapilot-design. The page opens on every screen, flow by flow, each one
a card with a live preview, its name, and its file; the entry screen of a flow is marked. A field
narrows the cards by screen or flow name. Click a card and that mockup opens as wide as
the window, as a site you browse: links inside it work, and the caption, the counter, and the address
follow you from screen to screen. All screens — or the browser's back button, or
Esc — returns to the gallery where you left it; the arrows and the ← → keys walk the whole
package in order, and the other screens of the same flow sit under the viewer. The address carries
the open screen (?screen=), so a link to one screen can be shared. A mockup folder that
is not a spec is named once, by its folder. Top-right Download zip packages the
presentation index.html, every mockup HTML file, local assets, and referenced
design-system files so the pack can be opened offline; Open index in new tab shows
that cover page. When /larapilot-design mocked several visual directions
(mockups/{spec}/styles/{slug}/), each style is listed on its own. Opening a screen
shows a style switcher and Use this style — the choice is written to
styles.yaml and logged in the decision journal, same as
larapilot:mockup-choose-style.
fernway.test/larapilot/design
Five screens of the member app, each tagged with the stories it covers.
Settings
Every project mode with its config.yaml key, current value, and what each option does —
the same keys as Project settings. Read-only: change them with
/larapilot-settings or larapilot:settings-set.
fernway.test/larapilot/settings
Gitflow, release mode, comments, and code history on; every other mode with what it would do.
Skills
Every skill the agents of the project can run, whoever brought it. The page
opens on who reads them here — the folder of each agent found in the project, with how many
skills it holds — and then lists the skills by where they come from. A field finds one by its
name, its package, or a word of what it does. Opening the page re-registers the
custom skills with Boost.
fernway.test/larapilot/skills
Claude Code and Cursor read the skills here; /studio-release-notes is Fernway's own, the 29 below come with Larapilot.
Group
Read from
What the page adds
Custom skills
.larapilot/skills/
Registered / Not registered
Packaged with Larapilot
The package itself
What is on screen is what the agent runs
Written for Boost in .ai/
.ai/skills/, .ai/{package}/skill/
The copy Larapilot registered of a custom skill is not listed as a second skill
From other packages
resources/boost/skills/ of every package in vendor/ and
node_modules/
A package that came with another one says so: Boost publishes the skills of a package
only when the project requires it itself
From Laravel Boost
vendor/laravel/boost/.ai/
The ones an agent has come first; the ones Boost ships for packages the project does
not use are folded. Of two versions under one name, the published one is read
Only in the folder of an agent
.claude/skills/, .cursor/skills/,
.github/skills/, .agents/skills/, and the others
No package and no folder of the project accounts for it: added by hand, or by another
tool
Under every skill, which agent has it: the folder of an agent holds a copy, and
the page says whether that copy is the text of the source, was made by Boost from a template, or
is not the same text — an old copy, which php artisan boost:update publishes
again. A version of a package skill the project keeps in .ai/skills/ is named, because
that is the one Boost publishes.
Click a skill to read it (/larapilot/skills/{name}). The page shows
the skill as a document, not as a file: the title, the slash command, and the description on top;
the front matter as facts instead of text; the Markdown rendered — headings, tables, task lists,
code — with a list of its sections that marks the one being read; the files that sit beside
SKILL.md; and Download SKILL.md for the file as it is on disk. A
packaged skill is read from the package itself, so what is on screen is what the agent runs. Raw
HTML in a skill is shown as text, never run.
When several skills carry one name, the address says which one is meant
(/larapilot/skills/{name}?from=boost.tailwindcss-4) and the page lists the others.
A skill written as a Blade template (SKILL.blade.php) is
never run by the dashboard: it is shown as Boost published it in the folder of an
agent, or as it is written when no agent has it. The name and the source are looked up in what is
on disk, never turned into a path, and a link that leads out of the project is not followed.
What the agents are told
A skill runs when it is called. Under the skills, the page lists what is read
before any request: CLAUDE.md, AGENTS.md,
GEMINI.md, .github/copilot-instructions.md,
.junie/guidelines.md, the rules in .cursor/rules/, and what the team
wrote for Boost in .ai/guidelines/ and .ai/rules/. Each card says which
agent reads the file and how many parts of it each author wrote.
A click opens /larapilot/skills/guidelines/{id}. Boost writes the guidelines of
every package into those files between two markers, one part for each; the page cuts the file
where Boost cut it and says who wrote each part:
Author
How it is known
Boost
A part Boost names itself: foundation, boost,
php, laravel/core, pest/core, …
Larapilot
andreapollastri/larapilot/core
Package
A part named after a package that is installed and brings
resources/boost/guidelines/
Project
A part named .ai/…: the guidelines of the team
By hand
What is before and after the markers. Boost keeps it; what is between them is written
again at every boost:update
A file Boost did not write is read as one document, with the list of its sections and its front
matter as facts.
Security
The findings of Aikido for this repository. The page is always in the menu; with
aikido off it says what Aikido is and how to connect it. Full
chapter: Aikido.
Errors
What the running application threw, as the tracker of the project recorded it — Boogle, Sentry,
Bugsnag, Flare, Datadog, Rollbar, Honeybadger, or CloudWatch. The page is always in the menu; with
errors off it says what Boogle is, how to connect it, and which other trackers can be
read instead. Full chapter: Production errors.
Git
Three views of the local repository. None of them calls GitHub, GitLab, or any remote API.
fernway.test/larapilot/git
The history of Fernway: stories merged into the release branch, one branch pushed and one only on this machine.
Contribution calendar — every commit on every local branch for the last 12
months. The grid fills the panel width, today sits on the right, and the developer
dropdown shows one author's squares.
Branches — every local branch with its type (main,
develop, feature, bugfix, release,
hotfix), its last commit, and where it stands against the branch it is heading
for: work goes into the integration branch, what ships goes into the main line. The state
is a sentence — 3 commits to merge into develop, 3 commits to merge, 8 behind
develop, Merged into main — beside a bar of commits behind and ahead. The branch
checked out is marked You are here; a branch that names a spec
(feature/US-004-invoice) links to it. On the remote says whether a branch
was never pushed, has commits to push or pull, or was deleted upstream. Merged branches fold into
one line, because their work is already where it was going; branches that exist on the remote only
are listed apart.
History — the last 150 commits (300 or 600 on request) drawn as a graph, newest
at the top. The main line keeps the first lane and the integration branch the second, so the
picture reads the same from one repository to the next. A filled dot is a commit, a hollow one a
merge; a curve leaving a dot shows where a branch came from or what it merged. Labels mark the
head of each branch and every tag; each row names the branch the commit was made on, its author,
its date, and its hash, linked to the forge when the remote is known. With a developer selected,
the commits of the others fade.
Git does not record which branch a commit was made on, so the page works it out the way a person
reads a history: a branch owns what its head reaches through first parents — the main line first, then
the integration branch, then releases and hotfixes, then the rest — and a branch that was
merged and deleted is named from the message of the merge that closed it
(Merge branch 'feature/US-002-reset' into develop, Merge pull request #12 from
team/feature-x). The main line is main, master, trunk, or
the default branch of origin; the integration branch is develop,
development, or dev. A repository with neither is still drawn, read against
the branch that is checked out.
The graph uses four colours and a grey — main line, integration, work branches, releases and
hotfixes, other — checked for colour-blind separation on both themes. Colour is never the only sign:
every row names its branch.
Usage
Lucille's token and hour ledger — totals, category bars, history, and a one-click Markdown report at
/larapilot/usage/report.md (same data as larapilot:usage-report --insights).
Deadlines and the Gantt live on Plan. Reference: Usage &
Lucille.
fernway.test/larapilot/usage
Eight delivered stories: estimated at 91.5 hours, built in about 27, one sent back once.
Economics
What the project costs, what the client pays, and what is left for you, written for someone who has
never read a balance sheet — when account is FREELANCE or
COMPANY. Full chapter: Economics.
Docs
The delivery loop, optional branches keyed off the current settings, every packaged skill with its
primary output and lead personas, and the persona roster.
fernway.test/larapilot/docs
The delivery loop and its side paths, as the settings of the project have them.
Spec detail
Click any card to open /larapilot/specs/{code}:
fernway.test/larapilot/specs/US-006
US-006 in review: the header and the story line by line, as the spec template writes them, and the status the backlog holds.
User story — acceptance criteria and narrative from the spec YAML, each line as the
spec skill wrote it — the header, the blocker, the three lines of the story — and the
**Status:** of its header kept in step with the backlog at every change of status
Decision journal — entries for this spec from .larapilot/decisions.yaml
(superseded context and regression guard)
Mockups — when /larapilot-design created HTML in
.larapilot/mockups/{code}/, an embedded preview and screen links appear (served via
/mockups/{code} in dev/staging). All mockups also live in the
Design gallery at /larapilot/design
Internal feedback — accordion list of PM/dev comments (author, date, status, preview);
blocking items show a Needs rework badge. Post new comments with a Markdown toolbar and
compact footer (blocking checkbox, log path) until the story is DONE. Toggle with settings.comments /
larapilot:settings-set --comments=YES|NO; LARAPILOT_COMMENTS_ENABLED=false is a
deployment kill-switch — see Comments.
Technical plan — plan Markdown once /larapilot-plan has run
Tasks — expandable subtasks with status, type, body, and commit SHA when marked done
Status badge, story points, and merge commit appear in the header. Download spec (.md)
(/larapilot/specs/{code}/spec.md) saves the whole spec in one file: the facts of the
header, the user story, the technical plan, every task with its status, type,
estimate, dependencies, commit, and body, the decision journal, and the internal feedback, oldest
first, under a note to remove it before the file goes to a client. Headings inside a story, a plan,
or a task are moved under the heading of the file, so the outline stays in order. The dashboard
mirrors repo state — skills remain the only way to change it.
Economics
Two things wear the same name. /larapilot-economics is the skill — a
conversation with Aurora that calibrates who is selling the work and then writes the client quote.
/larapilot/economics is the page — an interactive pricing tool that
recomputes that same maths live while you move dropdowns. The skill decides; the page explores.
Both read one engine (EconomicsService::snapshot()), so they can never disagree.
Everything below needs settings.account to be FREELANCE or
COMPANY — on NONE the page says so and stops.
fernway.test/larapilot/economics
Fernway sold as a subscription by a UK limited company, at £49 a month.
How the skill runs. Short rounds, at most three questions each, current value
always marked, skipped answers never re-asked:
Country & regime — tax residency, then the regime from the FY-2026
catalogue (forfettario, ordinario, SRL, Ltd, GmbH, …). Aurora offers only
regimes the catalogue actually has; it never invents a rate or a bracket.
Rate & margin — hourly rate, target margin, monthly overhead, commercial
discount, team size. The discount comes out of the margin, not the cost, and Aurora says
once when it has priced the project under what it costs to deliver. Team size compresses the
calendar and never the price.
Product model — how the project makes money, the four options in the table
below. If it lands on SaaS, the same round asks monthly list price, churn, planning customers, and
the hosting baseline.
Market research — Jennifer (positioning) and
Benjamin (market) go and find named competitors with their real list prices, the
price trend, demand scenarios, and packaging tiers, and persist the result with
larapilot:economics-market-write. Offered by default for subscription and product
work; on a one-off client delivery it is skipped unless you ask — the page says
as much under No competitor data. Nothing here is invented: an unknown price is left out
rather than guessed, and every figure carries its source URL.
Present — Aurora reads the snapshot back in a fixed order: quote, net to owner
with the effective tax rate, where the hours come from plus any warnings, and — for SaaS — the
three price lines and the three business-plan readings.
Write the client quote — a commercial document, in the PRD's own language, any
language, through larapilot:economics-quote-write.
The skill touches nothing else: not the PRD, not the backlog, not the code. It writes the economics
profile, the market research, and the quote document, always through the CLI — never by editing
economics.yaml by hand.
What you end up with.
Artifact
Where
What it holds
Account profile
.larapilot/economics.yaml
Country, regime, rate, margin, discount, team size, overhead, maintenance %, product model,
subscription inputs. Written only by larapilot:economics-set.
Computed snapshot
.larapilot/economics.snapshot.yaml
The whole quote as the engine computed it — effort per spec, tax, payback, packaging,
business plan, market. Refreshes itself whenever specs, plans, the PRD, or
inception change, so the cost board follows the backlog with no manual trigger.
Market research
.larapilot/economics.market.yaml
Sector, segment, named competitors with price and trend, demand scenarios, tiers, risks,
sources. Written by Jennifer and Benjamin, plotted by Larapilot, invented by nobody.
Client quote
.larapilot/docs/quote.md
The commercial document you send: offer summary, objectives, what the client gets,
infrastructure and hosting, security and quality, investment table, timeline, payment
milestones, what is and is not included, signatures. No story points, no tax breakdown, no
architecture. Download it at /larapilot/economics/quote.md; convert with
pandoc quote.md -o quote.pdf. Until one is written, a built-in
en · it · es · fr · de · pt · nl · pl template serves the download.
Internal report
/larapilot/economics/report.md
The engineering-side read: taxable base, contributions, extraction, scenarios. Never shown
to the client.
JSON
/larapilot/api/economics
The same snapshot for an agent, and it accepts the same what-if parameters as the page
(?hourly_rate=70&tier=premium&discount_pct=10).
The page is written for someone who has never read a balance sheet. Three rules
hold from top to bottom: the answer comes before the detail, no figure appears without the sum that
produced it, and a word of finance appears only in the glossary, next to the plain words the rest of
the page uses for it.
Block
What it answers
How it shows it
The short answer
What happens, in one sentence
The sentence, then four figures. A one-off build: what the client pays, what you keep (and
how much of every 100 of the price that is), the time to deliver, the upkeep each year. A
subscription: the cost to build, what one customer leaves, the customers that cover the
monthly bills, and the month the build is paid back — or not within 3 years
Try other numbers
What if the rate, the margin, the price were different
The console of dropdowns. Pointing at a control, or reaching it with Tab, writes
what it changes under the console. A fold you opened stays open when a control redraws the
page
The subscription, one step at a time SaaS
Is a subscription worth it
Five numbered steps: one customer, one month (price − card fees − support = what
one customer leaves), what the product costs every month (hosting + keeping the code
healthy + the share of your running costs), how many customers it takes with both
divisions written out, when the money comes back, and the three forecasts side by
side
What the client pays and what you keep
How the price is built, and where it ends up
Two receipts, each under a bar drawn to scale. How the price is built: work +
running costs + margin (− discount) = client price, + VAT = what the client pays.
Where the money goes: client price − running costs − tax and contributions −
accountant (− what the law keeps in a company) = what you keep. Then the same thing in one
sentence: out of every EUR 100 of the price, EUR 71 stay with you…, and what that
makes for each hour of work
After delivery
What recurs once the build is paid
The maintenance retainer and what it is priced on, the share of your working year the
project takes, and — by model — the orders or the licences that repay the build
PackagingSaaS
What three prices would do
BASE, PRO, and PREMIUM side by side, each with what one customer leaves and the customers
it takes at that price
Where the hours come from
What the price rests on
Hours by release or by epic as bars, then every user story with the source of its
hours. Once a spec is delivered, one line for you alone: the hours the quote counts for the
delivered specs and the time the agent took to build them — never in the client quote
The market
Where the price sits
Researched competitors, the price trend, and your position against the median
Words used here
What the terms mean
VAT, margin, net, fixed costs, MRR / ARR, churn, contribution, break-even, LTV : CAC,
person-months — each defined in one or two sentences, with the figure it has in this
project
When the money comes back is a chart of what the product has earned so far,
everything counted. The line starts below zero by the cost of the build, each month adds what the
subscriptions leave after the bills and tax, and where it crosses zero the product has paid for
itself. The forecast in use is the coloured line; the other two are grey. Moving over the chart — or
the arrow keys, Home, End, Page Up, Page Down once it
has the focus — reads any month: customers, what they paid, what was left, the total so far. Under
the chart the same story is told in sentences: Launch — you are EUR 5,129 down,
Month 20 — the subscriptions cover the monthly bills, After 3 years — there are still
EUR 4,218 to earn back. Month by month holds the table. Each forecast carries a
verdict in words: pays for itself in month 24, covers its bills from month 20, not
the build yet, or loses money every month.
The bars use two sets of three colours — what a price is made of (work, costs, margin) and where
it ends up (kept, tax, costs) — checked for colour-blind separation on both themes, with costs the
same colour in both. A figure is always written beside its colour, and a verdict is an icon and a
sentence, never a colour alone. The page reads in the PRD's
language (en · it · es · fr · de · pt · nl · pl, English
otherwise), like the client quote; the console stays in English on purpose, because its labels and
the economics-set command it prints are operator controls.
Eight languages, all or nothing. Detection
(ArtifactLanguage) and the three places Larapilot writes prose — the Economics page and
engine, the client quote, and the design presentation — are kept in step by a test: a language listed as
supported but missing from one of them fails the build. That is deliberate. A half-translated language
renders English in one artifact and not the others, which is worse than not offering it. Agent-written
documents are not limited to this set: /larapilot-economics writes the client quote in
whatever language the PRD uses, and the built-in template is only the fallback for projects where nobody
wrote one yet.
Nothing is written from the browser. Moving a dropdown is a
simulation: the page recomputes server side through the same snapshot the CLI uses and prints the
exact php artisan larapilot:economics-set … command that would make it the real profile.
Out-of-range values are dropped rather than clamped, so an edited URL cannot push a figure the engine
would refuse.
Sold as — the four ways a project makes money
The Sold as toggle decides which half of the page is the real one. It never changes
the hours or the build price — those come from the backlog either way. It changes the question the
page answers about getting the money back.
Sold as
What it means
What the page computes for it
One shot — fixed price fixed
The client pays once for the build. After go-live only the maintenance retainer recurs.
The classic agency or freelance commessa.
Payback read as capacity: what share of your working year the project
takes, how many projects like it fit in a year, and what a year filled with work priced this
way would leave you after tax. No packaging, no subscription forecast. Market research is
skipped unless you ask for it.
SaaS — subscription saas
The build is money you put in and earn back from monthly subscriptions. The client is the
market, not one buyer.
The whole second half of the page turns on: BASE / PRO / PREMIUM plans cut
out of the backlog, contribution per customer after payment fees and support, break-even
customers, a 36-month business plan read pessimistic / realistic /
optimistic, hosting costs, LTV:CAC — plus the Subscription group in the console (list price,
price line, scenario, churn, growth, planning customers). Market research is offered by
default, and researched competitors are plotted against your price.
E-commerce ecommerce
Priced like a one-off build — you invoice the shop, you do not sell subscriptions — but the
shop earns its build back on order volume.
The one-shot price, plus payback read in orders: how many orders a month
the shop has to take to repay the build inside a year. The average order value and the take
rate behind that figure are engine assumptions, not researched numbers, and
the page says so — read it as an order of magnitude, not a forecast.
Licensed package package
Priced like a one-off build, then repaid by many licences instead of one client — a
product, a plugin, a licensed library.
The one-shot price, plus a suggested annual licence price and how many
licences repay the build. It uses the annual price on your profile when there is one;
otherwise it suggests a fraction of the build and tells you that is what it did.
Left on its default (auto), the model is inferred from the Business Model
answer at inception, then from the PRD. The dropdown always wins over both, and the same four values
are accepted by larapilot:economics-set --product-model=. Every one of them is explained
in place on the page, under What “Sold as” changes.
API
JSON API over the same artifacts as the dashboard. Read endpoints for scripts, CI dashboards,
external frontend repo setup, bug-triage diagnostics, or
tooling; POST /specs/{code}/comments appends internal feedback when enabled. Workflow state
still changes only via skills/CLI.
Activation
API routes share the dashboard gate: enabled when dashboard activation
conditions are met. Base path: /{prefix}/api (default /larapilot/api). Returns
404 in production or when disabled.
Authentication
Set LARAPILOT_API_TOKEN to require a shared token on every API request — send it as
Authorization: Bearer <token> or an X-Larapilot-Token header. When the token
is set it is enforced on every endpoint, reads and writes alike. Without a token configured,
read endpoints stay open in the allowed environments, but write endpoints (POST /comments) are
refused outside local/development/testing.
The api_auth project setting (default NO) makes the token
mandatory: with api_auth=YES every /larapilot/api/* call — the
/diagnostics, /openapi.json, and /docs endpoints included — requires
LARAPILOT_API_TOKEN, and the API fails closed (HTTP 503) when the env var is not
set instead of answering unauthenticated. Turn it on with
php artisan larapilot:settings-set --api-auth=YES. Client setup and curl /
Laravel HTTP / fetch / CI examples: .larapilot/integrations.md → API access.
The optional dashboard_auth setting (Basic Auth on the
dashboard UI) does not apply here, and api_auth does not apply
to the dashboard UI — the JSON API is gated only by LARAPILOT_API_TOKEN /
api_auth and the dashboard activation conditions.
Metrics, status order, and specs grouped by workflow column — each spec includes
mockups.screens (absolute preview URLs) and a counts-only feedback
summary
GET
/larapilot/api/specs
Specs with task progress, mockup screens, and counts-only feedback summaries. Optional
?status=TODO filter; paginated with ?page / ?per_page
(1–200, default 50) — body carries total, page, per_page,
total_pages
GET
/larapilot/api/specs/{code}
Single spec with plan body, tasks, workdir, task progress; each spec embeds
mockups.screens (absolute preview URLs) and feedback.entries
Delivery snapshot — backlog completion, plan/task progress, (when the Lucille ledger is on)
an effort-timing block: tracked hours, tokens, first/last activity, and build:
the hours the delivered specs were estimated at beside the time they spent
IN PROGRESS
GET
/larapilot/api/economics
Economics snapshot — quote, tax, payback, packaging, business plan, market
(enabled: false when account is NONE). Accepts what-if query parameters
(?hourly_rate=70&discount_pct=10&tier=premium) that are computed and returned,
never stored
GET
/larapilot/api/backstage
Backstage catalog entities, rendered catalog-info.yaml, TechDocs metadata, and a
lean delivery snapshot for a portal plugin. Full guide:
Backstage portal.
GET
/larapilot/api/backstage/catalog-info.yaml
The same entities as a multi-document YAML descriptor — consumable as a Backstage
url location
GET
/larapilot/api/diagnostics
Read-only runtime snapshot for bug triage — see Diagnostics
The API is a development and staging tool: it is never registered in production, and on a shared host
it belongs behind LARAPILOT_API_TOKEN with api_auth=YES. Mockup preview URLs are
absolute in API
responses when the mockup route is browsable. Comment writes need comments=YES, honor
LARAPILOT_COMMENTS_ENABLED, and reject DONE specs with 422. Diagnostics honor
LARAPILOT_DIAGNOSTICS_ENABLED and redact secrets in log tails.
API hardening
Four protections that are always on for /larapilot/api/*, whatever
api_auth says.
Rate limit — per IP, from larapilot.api.rate_limit
("max,minutes", default 120,1). Over budget → HTTP 429 with
Retry-After. LARAPILOT_API_RATE_LIMIT=0 disables it.
Audit log — every mutating request
(POST /specs/{code}/comments) appends one JSON line to
.larapilot/api-audit.log (timestamp, method, path, IP, whether a token was sent, status —
never bodies). Git-ignored automatically. LARAPILOT_API_AUDIT=false to turn off.
Conditional requests — GET /board, /specs,
/specs/{code}, /prd, /metrics return an ETag; send it
back as If-None-Match for a 304 Not Modified. Ideal for pollers.
Security headers — every dashboard and API response carries
X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer; dashboard
pages also send X-Frame-Options: DENY.
Diagnostics and logs
What the running application says about itself, for /larapilot-bug,
/larapilot-error, and local debugging: the logs read as entries, and the
health of the runtime. Both only read, and both redact secrets.
LARAPILOT_DIAGNOSTICS_ENABLED=false turns both off.
Logs
php artisan larapilot:logs reads the log files of the application the way the
Logs page of the dashboard does, and answers with what an agent
needs: when, what, the exception and where it was thrown, and the frames of the
application. /larapilot-bug runs it every time, whatever the report says;
/larapilot-error runs it for every group it hands to triage.
Option
What it does
--group
One row for each thing logged, with how many times and since when — the most repeated
first. Without it the entries come one by one, the newest first
--level=warning
That level and every one more severe: debug, info,
notice, warning, error, critical,
alert, emergency
--search="…"
Every word must be in the entry, in any case; quotes keep a phrase together. A value that is
redacted is never found
--since=7d
Minutes, hours, or days back (30m, 1h, 7d), or a date
(2026-10-02)
--limit=
20 by default, 100 at most
--files · --file=
The log files there are · another one than the file the application writes to now. A file that
is not in Laravel's format has no levels and no dates: --level and
--since are left out, and the answer says so
The answer opens with what the file holds — the count of each level and the period it covers — and
says whole_file: false when the file is larger than
LARAPILOT_LOG_VIEWER_SCAN_MB (32) and only its end was read. With no log, or nothing that
matches, it answers with an empty list: nothing logged is a finding too. Through MCP it is
RunArtisanTool → larapilot:logs. The command needs no token and is not gated
by the dashboard; it reads the folder of the Logs page (LARAPILOT_LOG_VIEWER_PATH).
Health of the runtime
App status and health checks, with a tail of the log. Same dashboard gate as the rest of the API
(never in production); LARAPILOT_DIAGNOSTICS_ENABLED=false returns 404.
Surface
Usage
API
GET /larapilot/api/diagnostics — optional ?lines=100,
?no_logs=1