andreapollastri/larapilot
| Install | |
|---|---|
composer require andreapollastri/larapilot |
|
| Latest Version: | 5.2.0 |
| PHP: | ^8.1 |
| Laravel: | 10 – 13 |
| License: | MIT |
| Last Updated: | Oct 12, 2026 |
| Links: | GitHub · Packagist |
Larapilot
From product idea to reviewed Laravel code — with an AI product team that follows a real process.
Larapilot is a spec-driven workflow for Laravel projects, built on Laravel Boost. Install the package, run /larapilot-* skills in your AI editor, and get a PRD, a backlog, technical plans, and reviewed code — all versioned in .larapilot/ next to your app.
The agent proposes. You approve what ships. Human-in-the-loop, always.
📖 Documentation: larapilot.web.ap.it · Use cases · Custom skills · API
Contents
- Why Larapilot
- Quickstart
- The core loop
- Studio — Larapilot for the whole team
- Skills
- Custom skills — your own slash commands
- Workflow hooks — your commands on the loop
- What lands in
.larapilot/ - Developer domain docs
- Project settings
- Dashboard & API
- Economics
- Traceability
- Security
- Upgrades — Laravel, PHP, database
- Integrations
- Artisan CLI & MCP
- Requirements
- Learn more
Why Larapilot
AI agents are fast, but isolated prompts are not a product process. Larapilot gives your assistant a disciplined squad — discovery → backlog → plan → implement → review → ship — with 30 personas (Mark for product, John for architecture, Robert for review, Lars for security, Sarah for CLI/Git/Linux, Lucille for time and deadlines, …) used as review lenses, not costumes. Apps, websites, and PHP/Laravel Composer packages are all first-class.
Three layers, each with one job:
| Layer | Job |
|---|---|
Boost skills (/larapilot-*) |
Drive the conversation in your editor |
Artisan commands (larapilot:*) |
Persist state and enforce the workflow — skills never write workflow files by hand |
MCP server (larapilot) |
Lets the agent read backlog, specs, and diagnostics mid-conversation |
The workflow engine blocks invalid transitions: no implement before plan, no approve before review, no approve with open [blocks-merge] feedback or unfinished tasks (unless you pass --force).
Quickstart
composer require andreapollastri/larapilot --dev
php artisan larapilot:install
php artisan boost:install
larapilot:install scaffolds the .larapilot/ workspace, and also Larastan level 5+ and Laravel Pint (phpstan.neon.dist, pint.json, Composer scripts). When the project has neither package it adds them with composer require --dev, which leaves the formatting of composer.json alone, and Composer picks the versions your PHP and Laravel take — Larastan 2 on PHP 8.1 / Laravel 10, Larastan 3 from PHP 8.2 / Laravel 11.15. Run php artisan larapilot:quality before merge; larapilot:doctor fails when the gate is missing. On an app with years of code never formatted, see An existing codebase.
Already on Boost? Run php artisan larapilot:update: it adds andreapollastri/larapilot to the package list of boost.json when there is one — Boost publishes the guidelines, and on Boost 2 the skills, of the listed packages only — and republishes. By hand, choose the package in boost:install, or run boost:update --discover (Boost 2.3.2 and later).
The dashboard is at /larapilot: open on your machine (APP_ENV=local), behind a sign-in on any other environment — see Dashboard sign-in & roles.
If your editor does not list the MCP servers after boost:install, register them:
{
"mcpServers": {
"laravel-boost": { "command": "php", "args": ["artisan", "boost:mcp"] },
"larapilot": { "command": "php", "args": ["artisan", "mcp:start", "larapilot"] }
}
}
With Sail, the command is vendor/bin/sail and the arguments start with artisan.
Then, in your editor:
| Your project | Type |
|---|---|
| A new idea — app, site, or Laravel package | /larapilot-inception "your product idea" |
| A Laravel app in production, built without Larapilot | /larapilot-adopt |
| A legacy system you are rewriting in Laravel | /larapilot-inception with a snapshot in .larapilot/legacy/ |
An existing codebase
larapilot:quality asks Pint and Larastan for a clean result before every task is done. On an app that was never formatted, or that has Larastan errors today, that gate stays red. Two settings make it fair, and /larapilot-adopt proposes both:
php artisan larapilot:settings-set --quality-scope=CHANGED # Pint reads only the PHP files the branch changed
php artisan larapilot:quality --baseline # phpstan-baseline.neon: today's errors stay out, new ones do not
CHANGED measures a branch against the branch it came from (develop under Git Flow, else the remote default, main, master) — committed, staged, unstaged, and untracked files — so a task stays checked after its commit; on the base branch itself it is what is not pushed yet. --fix then formats only those files: a full reformat is a commit of its own, when you decide it. Larastan still reads the whole project, because a change can break a caller it did not touch; the baseline keeps the errors that were already there.
Laravel 10, PHP 8.1, and Boost 1
Boost 2 needs PHP 8.2 and Laravel 11.45.3 or later, so a Laravel 10 or PHP 8.1 app runs Boost 1.x. Larapilot works there, with three differences:
- Skills — Boost 1 publishes guidelines, not skills, and its 1.x line will not get them.
larapilot:installandlarapilot:updatecopy the/larapilot-*skills into the skill folders of the agentsboost.jsonnames (.claude/skills,.cursor/skills,.github/skills, …) and of the agent folders already in the project, rewriting only what changed and removing a skill the package retired.larapilot:doctorreports a copy that is missing or out of date. - Guidelines — Boost 1 adds a third-party guideline to
CLAUDE.md/AGENTS.mdonly when the package is in theguidelineslist ofboost.json. Chooseandreapollastri/larapilotinboost:install;larapilot:updateadds it when the list exists without it. - Updates —
larapilot:updaterunscomposer update laravel/boostonly when Boost 2 can install. On Boost 1 that it cannot leave,composer.lockis left untouched, andboost:updatestill refreshes the guidelines.
boost:update --discover is a Boost 2 option (2.3.2 and later): on Boost 1, run boost:install again to change what is published.
Sail and Docker
When Artisan runs in a container, the agent on the host has to go through it, and cannot open the container's paths (/var/www/html/...). Larapilot follows Boost: with "sail": true in boost.json (Boost asks during boost:install), every skill runs vendor/bin/sail artisan, and the JSON envelopes give paths relative to the project root. Any other setup names its command in .env:
LARAPILOT_ARTISAN="docker compose exec app php artisan" # empty: Sail when boost.json says so, else php artisan
LARAPILOT_AGENT_PATHS=auto # auto · relative · absolute
The Larapilot guideline tells the agent the command (re-run php artisan larapilot:update after changing it), and every larapilot:context envelope carries it as data.artisan. auto gives relative paths whenever Artisan runs through another command or inside Sail; a dev container, where the agent runs inside too, keeps absolute ones.
Sail publishes its port on every interface, so a dashboard left open on local is reachable from your whole network. Bind it to your machine: APP_PORT=127.0.0.1:80 in .env.
Upgrade
composer update andreapollastri/larapilot laravel/boost --with-dependencies
php artisan larapilot:update
php artisan larapilot:doctor
larapilot:update refreshes the runtime packs, task-templates.md, integrations.md, and the packaged design systems (--preserve-design-systems keeps yours), re-registers your custom skills, moves a handbook left in _project_docs/ by an older version into .larapilot/docs/handbook/, realigns the delivery forecast of a project that is already planned, then runs composer update laravel/boost (skipped inside a Composer script, and on a Boost 1 that cannot reach Boost 2) and boost:update — and, on Boost 1, copies the skills itself. It does not touch composer.json. Runtime-only refresh: php artisan larapilot:update --skip-boost. It never touches config.yaml (except to repoint an old paths.project_docs: _project_docs/), the PRD, plans, domain docs, or custom skills. In the backlog and the schedule it writes only what takes no decision — 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 the specs of that epic that lack it — and it says what the forecast now misses: dates that do not hold and inputs it could not read are for /larapilot-schedule. php artisan larapilot:schedule-apply --repair --dry-run lists those repairs without writing them. Do not re-run larapilot:install on an existing project unless you mean --force.
From 4.x to 5.0 — composer update stays inside the major your composer.json names, so raise the constraint first:
composer require andreapollastri/larapilot:^5.0 --dev --with-all-dependencies
php artisan larapilot:update
php artisan larapilot:doctor
If composer why andreapollastri/larapilot says requires rather than requires (for development), Larapilot sits in require: drop --dev, or Composer moves it to require-dev. Nothing in .larapilot/ is migrated: PRD, backlog, plans, decisions, and settings are read as they are. Three things to check: spec-list answers without the bodies (add --full where a script reads body); a custom skill that names a runtime part by its number should cite the heading, or start with larapilot:context {name} --with=…; /larapilot-boogle is /larapilot-error, and the boogle-* commands still answer. What v5 changes, measured: Version 5 vs version 4. Step by step — a branch, a dry run, the output to read, the checks as git grep commands, and the way back: Major releases.
From 5.0 to 5.1 — composer update and larapilot:update are enough, but three things change on a shared host:
- The dashboard asks for a user outside
local. HTTP Basic Auth and thedashboard_authsetting are gone. Every environment butlocal,testing, andproduction—staging,qa,preview, anddevelopmenttoo, which 5.0 treated as a developer's machine — shows a sign-in page. Until a user exists there, the dashboard answers HTTP 503. Make the first admin on that server:php artisan larapilot:dashboard-user add andrea --role=admin.larapilot:updaterewrites an.larapilot/auth.yamlof before 5.1 and gives every user in it theadminrole — the only one there was. See Dashboard sign-in & roles. - The API needs its token outside
local. It answers HTTP 503 until one exists: make it on the API token page or withphp artisan larapilot:api-token generate. Once a token exists, every request needs it — reads included, on a developer's machine too.LARAPILOT_API_TOKENin.envkeeps working. /larapilot-aikidois/larapilot-security. Theaikido-*commands and--aikido=YESstill work; aconfig.yamlwithaikido: truereads assecurity_provider: aikido.
The core loop
Greenfield — repeat steps 3–5 per user story:
/larapilot-inception "…" → /larapilot-spec → /larapilot-plan US-XXX
→ /larapilot-implement US-XXX → /larapilot-review US-XXX
Brownfield — adopt an app already in production:
php artisan larapilot:install → /larapilot-adopt → /larapilot-spec → (per-story loop)
| When | Start with |
|---|---|
| New product, site, app, PHP/Laravel package, pivot, or legacy rewrite | /larapilot-inception |
| Existing Laravel app in production, built without Larapilot, no PRD yet | /larapilot-adopt |
| One new capability on an existing product | /larapilot-feature "…" |
| Defect or regression | /larapilot-bug "…" |
| A request that may be either — a ticket, a client email | /larapilot-triage "…" |
| A change to the PRD that is neither — priorities, scope, a sharper requirement, a decision reversed, an older PRD brought up to date | /larapilot-prd "…" |
| Many planned stories at once | /larapilot-autopilot US-004 US-005 … |
| A team ritual the packaged skills don't cover | /larapilot-custom-skill |
Optional around the loop: /larapilot-design before plan · /larapilot-ship when the MVP stories are DONE · /larapilot-settings for project modes · /larapilot-usage for time and tokens · /larapilot-schedule to re-plan the order and the dates · /larapilot-economics for quotes and pricing.
Status machine: TODO → PLANNED → IN PROGRESS → REVIEW → DONE. A rejected review sends the story back to TODO with your feedback attached.
Git follows settings.git_mode (default GITFLOW, no auto-push): one feature/US-XXX-* branch per story, one atomic Conventional Commit per plan task; push and remote PRs only under GITFLOW_PUSH. With release_mode=YES, stories can branch from release/x.y.z instead of develop. Details: Git workflow.
Autopilot chains plan + implement for several specs, one at a time. Under STANDARD or MAX each spec runs in a fresh writing sub-agent; your session keeps the CLI transitions, questions, and the Robert/Lars review. Under ECO, or in an editor without sub-agents, it stays inline. You still run /larapilot-review per story unless auto_approve=YES.
Studio — Larapilot for the whole team
Larapilot runs where your agent runs: an editor or a terminal. Not everyone on a product team opens one. Studio — by the author of Larapilot, open source — 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.
One Ubuntu server, one script, one wildcard DNS record. Every person on the team gets a workspace on that server — their clone, their database, their own Claude Code — and a chat that runs the Larapilot skills of the project. You ask in plain words; Claude writes the story, plans it, implements it on a branch, and opens the pull request, while the live preview of that branch sits beside the chat.
- The loop, as buttons — New idea, Feedback, Status, and Approve under the chat; every
/larapilot-*skill, and every skill of the project, typed as a slash command. - The backlog beside the chat — Board, preview, changes, files, and a terminal in one pane; the site of the project serves
/larapilotto the team signed in to Studio. - Projects born with Larapilot — a repository Studio creates gets Laravel, Laravel Boost, and Larapilot, installed and committed; an existing one that runs Larapilot is cloned as it is.
- Four roles, one Claude per person — administrator, PM, developer, client; each conversation runs on that person's own Claude plan.
- The forges Larapilot knows — GitHub, GitLab, Bitbucket, or Azure DevOps; a push to the deploy branch redeploys the site.
# a fresh Ubuntu 24.04 or 26.04 server
wget -qO- https://raw.githubusercontent.com/andreapollastri/studio/refs/heads/main/installer/install.sh | sudo bash
📖 Studio: studio.web.ap.it · See a conversation · GitHub
Skills
Published by Laravel Boost after php artisan boost:install:
| Skill | Role |
|---|---|
/larapilot-inception |
Product discovery → PRD (includes Frontend Topology) |
/larapilot-adopt |
Reverse-engineer a PRD from an existing production codebase; derives the design system of the app as it is, so its mockups mirror the running product |
/larapilot-spec |
MoSCoW backlog from the PRD |
/larapilot-feature |
Mini-inception for one enhancement |
/larapilot-bug |
Bug triage → fix spec or rework. Reads the logs of the application every time, secrets redacted |
/larapilot-triage |
Bug or feature? Classifies a request against the PRD and the backlog, then hands off to /larapilot-bug or /larapilot-feature |
/larapilot-security |
Reads the security scanner the project associates — Aikido or Hackly — and asks which one when none is set. Downloads its open findings, confirms them with you, groups them by fix, and hands each group to /larapilot-triage (security=YES). Was /larapilot-aikido before 5.1 |
/larapilot-error |
Asks which tracker records the errors of production — Boogle, Sentry, Bugsnag, Flare, Datadog, Rollbar, Honeybadger, or CloudWatch — when none is set, downloads the open ones, confirms them with you, groups them by place in the code, and hands each group to /larapilot-triage (errors=YES) |
/larapilot-vendor-check |
Checks every dependency — Composer, the JavaScript of the repository, the frontend companion — against OSV.dev for known vulnerabilities, confirms each vulnerable package with you, then updates it, hands it to /larapilot-triage, or waives it with a reason |
/larapilot-laravel-upgrade |
Upgrades Laravel to the version you name: readiness report and criticalities first, then one major at a time on its own branch — dependencies, the upgrade guide, Filament, Nova, Livewire, Inertia — with an upgrade report |
/larapilot-php-upgrade |
Upgrades PHP: the suite on the target version, the packages that exclude it, deprecated code, every file that pins PHP (Docker, CI, Vapor, Herd, Sail), and the server runbook |
/larapilot-db-upgrade |
Upgrades or switches the database — MySQL 5.7 → 8.0 → 8.4, MySQL → MariaDB or PostgreSQL, a PostgreSQL major — with portable SQL, a local rehearsal on the target engine, and the data and cutover runbook |
/larapilot-prd |
Revises the PRD when it is neither — sharpen, re-scope, re-model, re-decide, upgrade — and aligns the stories that cite what changed |
/larapilot-design |
Static HTML mockups from a design system, with style variants to compare — the contract the frontend is built to. Presents the screens, takes changes at any later moment, and can interview you to build a design system of the client's own |
/larapilot-plan |
Technical plan + tasks for a spec; every UI task names the mockup screens it mirrors; proposes a sprint when sprints=YES |
/larapilot-implement |
Code + tests + developer domain docs, one commit per task; the UI mirrors the approved mockups, and a deviation is asked before it is coded |
/larapilot-review |
Human gate → DONE or rework; a silent deviation from the mockups is a High finding |
/larapilot-autopilot |
Batch plan + implement, one fresh context per spec |
/larapilot-ship |
Security gate + deploy runbook when the MVP is done |
/larapilot-settings |
Persist project modes (effort, backlog, git, testing, account, API token, sprints, public roadmap, security scanner, forges, notifications, …) |
/larapilot-release |
The versions of the product (release_mode=YES): which story targets which version, where a version stands, its release notes, the ship gate, and Gitflow release/x.y.z branches; keeps the public wording of the roadmap current — Versions |
/larapilot-project-docs |
Living handbook in .larapilot/docs/handbook/ (project_docs=YES) |
/larapilot-custom-skill |
Create your own skills under .larapilot/skills/, registered with Boost |
/larapilot-frontend-companion |
Link an external frontend repo or monorepo, name this product's projects, load its agent rules — driven from Laravel, or handed off to its team |
/larapilot-economics |
Aurora + Jennifer + Benjamin — quote, tax, payback, packaging, business plan, client quote (account ≠ NONE) |
/larapilot-usage |
Lucille — time/token ledger, deadlines, Markdown report |
/larapilot-schedule |
Lucille — re-plan a project that is already planned: order, estimates, epic deadlines, and milestones against the forecast, with a dry run before anything is written |
/larapilot-backstage |
Publish the repo into a Backstage developer portal |
/larapilot-tracker |
Mirror the backlog into Linear · Asana · Jira · Trello · ClickUp · Monday |
Inception is a conversation, not a questionnaire. AskQuestion is used only for the fixed choices Larapilot persists; every answer gets a reaction before the next question; and before any requirement is written, Mark, Jennifer, and Benjamin run at least two challenge exchanges on the goal itself (who has this problem and what they do instead, how you will know in 90 days, the riskiest assumption, what would make you stop). Four rounds always happen, legacy rewrites included: Project Kind, Delivery Target, Business Model, and Operations & support. A skipped round is recorded as Not decided, never as a guess.
Inception verifies before it scopes, and writes a PRD a spec can be built from. After the goal challenge, Sebastian runs a prior-art check (prior_art, ON by default): he states the queries, asks consent, searches GitHub, Packagist, and open-source catalogs for products or packages that already do it, writes research/prior-art.md (license, stack, last release, what it covers and lacks, adoption cost), and you record a verdict — Build anyway, Adopt / fork, Integrate as dependency, or Not checked. Then the PRD gets its nouns and paths before its features: ## User Journeys (one per way a persona gets value — the unit a spec is cut from), ## Domain Model (entities, states, relations, glossary), every ### FR-XXX with a named actor and verifiable Done means bullets, ## Non-Functional Requirements with a target and a verifier per row, and ## Risks & Assumptions (riskiest assumption, kill condition, open questions with an owner, the Not decided list). Tom runs a ten-point Definition of Ready, Mark reads the decisions back in twelve lines, and only then is the PRD written. validate-prd reports the new sections as warnings, so older PRDs stay valid.
Versions and sprints in the skills
- With
release_modeon,/larapilot-inceptionproposes the release roadmap — which stories go in which version, the MVP first — and asks for the starting release./larapilot-featureproposes a target version for every new spec: an open release, or a future one. The spec carries it as**Release:** x.y.z, and the Board, the Plan, the roadmap, and the API read it from there./larapilot-releasekeeps the public wording of the roadmap current after every release change. From there every skill works inside a version — see Versions. - With
sprintson,/larapilot-featureand/larapilot-planpropose the two-week sprint a spec belongs to and write it withphp artisan larapilot:spec-sprint US-012 3(noneclears it). With the setting off, nobody asks.
Mockups are the contract of the frontend
/larapilot-design, /larapilot-plan, /larapilot-implement, and /larapilot-review read the same rules:
- The build mirrors the approved mockups — layout, components, copy, states, responsive behaviour, dark mode. Every UI task of a plan names the screens it mirrors on a
Mirrors:line, and implement opens them before the first line of frontend code. - A deviation is asked before it is coded. When the code cannot follow a mockup — a technical limit, accessibility, a mockup that contradicts an acceptance criterion — the agent says what and why in chat, and you accept it, keep the mockup, or have the mockup changed first. An accepted deviation is logged in the decision journal and in the
## Deviationstable of the mockup README. A deviation nobody agreed to is a High review finding. - The mockups stay the mirror of the product. After drawing,
/larapilot-designpresents the screens and asks for feedback; you can ask for changes at any later moment, and each one is recorded in the## Revisionslog of the mockup README. - A design system of the client's own.
/larapilot-designoffers an interview — brand, references, links, the files you drop in.larapilot/brand/andclient-materials/, tone, density, components, accessibility, dark mode — and writes.larapilot/design-systems/{slug}/in the shape of the packaged ones. You approve it before any mockup uses it. - An adopted project mirrors what exists.
/larapilot-adoptderives the design system of the app from its views, CSS, brand assets, and navigation, and every mockup of that project is drawn in it. A redesign happens only when you ask for it, and is discussed first.
Changing the PRD after inception
The PRD changes through three doors, and each one leaves a row in ## PRD Revision History:
| The change is | Use | What it does to the PRD |
|---|---|---|
| One new capability, or a shipped one that must now behave differently | /larapilot-feature |
New FR-XXX, or the FR edited in place on a change request |
| A defect that shows a requirement was never written | /larapilot-bug |
A done-means bullet under the parent FR, or an NFR row. Never a "fix FR" |
| Anything else | /larapilot-prd |
See the revision kinds below |
| Revision kind | Example | Backlog impact |
|---|---|---|
| Editorial | Typos, a clearer sentence, a glossary term | None |
| Sharpen | Done-means for an FR, a target for an NFR, an open question answered | Criteria added to open stories |
| Re-scope | MoSCoW change, In Scope ↔ Future Phases, Delivery Target, an FR retired | Stories created, deferred, or deleted |
| Re-model | Entity renamed, state added, journey split | Stories citing the old names |
| Re-decide | Business model, ops owner, topology, data store, prior-art verdict | Architecture stories; quote recomputed |
| Upgrade | An older PRD gains journeys, domain model, NFRs, risks | None |
| Pivot | New vision or target user | /larapilot-inception, current PRD as input |
- Ids are permanent.
FR-,J-,NFR-, andQ-ids are never renumbered or reused. A dropped requirement keeps its heading withMoSCoW: Won'tand aRetiredline.validate-prdwarns onPRD_DUPLICATE_IDandPRD_DANGLING_REFERENCE. - The backlog follows.
php artisan larapilot:prd-impact --ids=FR-004,J-001lists the stories that cite the ids, with the action each needs:TODOre-issued,PLANNEDre-issued and planned again,IN PROGRESSon your consent,REVIEWsent back withspec-request-changes,DONEnever reopened. Without--idsit traces the whole PRD and lists the Must requirements no story covers. - Nothing is written before the readback. Mark shows before → after per changed item and asks Apply · Revise · Cancel.
- You edited
PRD.mdby hand? Run/larapilot-prd "I edited the PRD by hand". It reads the git diff and adds what a hand edit skips: the history row, validation, the inception snapshot, and the backlog check.
Context economy — what a skill loads
A skill opens with one command, php artisan larapilot:context {skill}. It answers the settings, the paths, what the PRD says about the project (kind, delivery target, budget, topology, …), and the runtime files that skill reads. Three things keep the context small, and keep an agent from applying a rule that is not the project's:
- Only what the skill needs. Every skill has its own list of packs. The heavy ones — tenancy, CI/CD, integrations, UX, deploy platforms — are named with the moment they apply and read only then.
- Only what the settings call for. The packs are compiled for the project into
.larapilot/cache/runtime/: underGITFLOWan agent reads theGITFLOWrules 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: the skill reads the new ones and nothing else. After the conversation is compacted,--freshreads everything again — a summary keeps the token and loses the rules.
| What is 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 |
Commands answer with the slice, too: spec-list is the backlog without the bodies (--full for everything), and prd-show reads the PRD by the piece — its outline, --ids=FR-004,J-001, or --section="Technical Architecture". A custom skill gets the same treatment: larapilot:context my-skill --with=delivery-1,dev-docs.
.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.
Custom skills — your own slash commands
The 29 packaged skills are the base layer. On top of them, each project can keep its own Boost skills in .larapilot/skills/ — committed with the code, registered with Boost automatically, built on the same larapilot:* CLI. Good candidates: a pre-deploy GO/NO-GO gate, a compliance check, client release notes, your team's house rules for a Filament resource.
Example — turn a pre-deploy checklist into /acme-predeploy-gate:
/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 you in three rounds: one skill or a family · slash name and trigger description (the YAML
descriptionBoost routes on) · personas, runtime packs, and the CLI commands in order. - She shows the draft
SKILL.md— front matter, Shared Runtime, Team, Config & CLI, Workflow, Output Economy — the same shape as a packaged skill. - Sarah saves it with one command, which validates it, writes the canonical copy, mirrors it into
.ai/skills/and every agent skill folder that already exists (.claude/skills/,.cursor/skills/, …), and runsboost:update:
php artisan larapilot:custom-skill-add --name=acme-predeploy-gate --file=.larapilot/tmp-acme-predeploy-gate-SKILL.md
{
"schema": "larapilot/v1",
"kind": "custom_skill",
"data": {
"skill": {
"name": "acme-predeploy-gate",
"relative_path": ".larapilot/skills/acme-predeploy-gate/SKILL.md",
"registered": [".ai/skills/acme-predeploy-gate", ".claude/skills/acme-predeploy-gate"]
},
"boost_published": true
}
}
- Type
/acme-predeploy-gatebefore the next deploy. Commit.larapilot/skills/; teammates runphp artisan larapilot:custom-skill-listaftergit pullto register it on their machine.
A custom skill never replaces one it did not write. A name of a Larapilot skill (larapilot-*) or of a skill Boost ships is refused — on the command line and in the file manager — and a folder of the same name that Larapilot did not put in an agent skill folder is left as it is, and reported. Larapilot knows its own copies by a .larapilot-custom-skill file inside them; custom-skill-add --force takes the name over.
The draft the example produces:
---
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
## Context
`php artisan larapilot:context acme-predeploy-gate` — read what `data.runtime.read` lists; `data.settings` is in that envelope.
## Workflow
1. `larapilot:spec-list --status=REVIEW` and `--status="IN PROGRESS"` — anything listed → NO-GO.
2. `larapilot:doctor` — `data.healthy` false → NO-GO.
3. `larapilot:quality` — an `E_QUALITY` error → NO-GO.
4. When `security_scan` is `YES`: `php artisan checkpoint:scan` — FAIL → NO-GO (or a logged waiver).
5. `larapilot:metrics` — one scope line; then GO / NO-GO, and `larapilot:notify` when notifications are on.
Stop at the first NO-GO. Never deploy, push, tag, or merge from this skill.
| Rule | Behavior |
|---|---|
| Name | kebab-case, 1–63 chars; --name overrides the front matter |
| Front matter | description is required. A file with no front matter gets a placeholder one — replace it |
| Overwrite | refused without --force |
| Input | --file=, --content=, or stdin |
| Register again | custom-skill-list, larapilot:update, and the dashboard Skills page re-register every skill on disk |
| Remove | delete the folder from the dashboard File manager — it removes the mirrors in .ai/skills/ and the agent folders that still match the skill — then php artisan boost:update. By hand: delete .larapilot/skills/{name}/ and its mirrors. Registration never deletes a copy |
Full walkthrough: Your own skill · Contract: Custom skills.
Workflow hooks — your commands on the loop
hooks, OFF by default. 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, in .larapilot/hooks.yaml (committed, so the team shares them):
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
| Event | Fired by | Event | Fired by |
|---|---|---|---|
prd.written |
prd-write |
spec.review |
spec-review |
spec.added |
spec-add |
spec.approved |
spec-approve |
spec.planned |
spec-plan |
spec.changes_requested |
spec-request-changes |
spec.started |
spec-start |
release.shipped |
release-ship |
task.done |
task-done |
ship |
/larapilot-ship: before as the gate starts, after on a GO |
| When | If it fails | |
|---|---|---|
before |
After the command's own checks, before anything is written | The transition is refused — E_PRECONDITION with details.hooks — and nothing is written. blocking: false only warns |
after |
Once the state is written | Reported under data.hooks.after.warnings; the transition stands |
run: |
Larapilot runs it from the project root, with the event in LARAPILOT_HOOK_* variables and as JSON on stdin |
The answer carries the last 40 lines; the whole output is in .larapilot/cache/hooks/ |
skill: |
The agent runs it. Before a transition, the command refuses until the agent reports it with --skill-hooks-done=name; after one, the answer lists it under data.hooks.after.skills |
— |
What teams use them for:
- Gates an agent cannot skip — tests, Larastan,
npm run build,composer audit, a coverage floor beforetask.doneorspec.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-shipor afterrelease.shipped. - Your rituals at the right moment — a custom skill as an architecture review after
spec.planned, an accessibility pass beforespec.review, client release notes afterrelease.shipped. - What Larapilot does not integrate — Teams, Mattermost, or email; a Toggl or Harvest timer on
spec.started; a Confluence or Notion export afterprd.written; an n8n, Zapier, or Make webhook.
php artisan larapilot:settings-set --hooks=YES
php artisan larapilot:hook-list # what is defined, and what is wrong with the file
php artisan larapilot:hook-run task.done --phase=before --spec=US-001 --task=TASK-01 --dry-run
- Nothing runs until you say so. Install writes
hooks.yamlwith every event listed and every example commented out, andhooksis OFF.LARAPILOT_HOOKS_ENABLED=falsein.envruns none on one machine — a CI runner, a teammate without the tools a hook calls. - A file with errors is a closed gate. While hooks are on, every transition is refused until
hook-listis clean or hooks are off;doctorreports it aschecks.hooks. - Artisan only. Hooks never run from the dashboard, the API, or MCP, where
hook-listis read-only./larapilot/settingsshows 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;
--forceonspec-reviewandspec-approveskips the task and feedback checks, never 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.
What lands in .larapilot/
| Path | Purpose |
|---|---|
config.yaml |
Connector, paths, and project settings — committed, so the team shares one mode |
docs/PRD.md |
Product Requirements Document — the living product contract |
backlog.yaml · specs/US-XXX.yaml |
User stories with their status machine |
plans/US-XXX-plan.yaml |
Technical plans and tasks per spec |
docs/devs/ |
Developer domain docs — why the code is built the way it is (always on) |
docs/handbook/ |
Handbook — the living technical + functional manual of the project (project_docs=YES); only its README until then |
docs/review/ · test-results/ · security/ · launch/ · support/ |
Review findings, test evidence, OWASP assessments, launch checks, bug intake |
docs/quote.md |
Client quote in the PRD language (Economics) |
choices.yaml |
Snapshot of inception answers (kinds, targets, prior-art verdict, success signal, kill condition, ops) |
decisions.yaml |
Append-only journal of your explicit choices + regression guard (decision_log, ON) |
hooks.yaml |
Your commands and skills on the transitions of the loop — every example commented out until you write one (hooks, OFF) |
code-history.yaml |
Files and line ranges touched per spec/task (code_history, OFF) |
releases.yaml |
Semver release ledger (release_mode) |
docs/releases/vX.Y.Z.md |
The release notes of each version, kept by release-notes (paths.release_notes) |
tracker.yaml |
Spec → tracker issue ids — commit it, or every machine creates duplicates |
economics.yaml · economics.snapshot.yaml · economics.market.yaml |
Economics profile, last computed quote, researched competitors |
usage/ |
Lucille ledger (ledger.jsonl) and schedule |
mockups/{spec}/ |
Static HTML previews; styles/{slug}/ for style variants |
internal-feedback/{code}.md |
PM/dev comments until DONE |
skills/{name}/SKILL.md |
Your custom Boost skills |
client-materials/ · legacy/ · research/ · brand/ |
Inputs for inception, legacy snapshots, prior-art / reference-product / parity reports, brand assets |
design-systems/ |
The 15 packaged design systems (see Design systems) — add your own beside them |
shared-runtime.md · runtime-*.md · task-templates.md · integrations.md |
Rules and guides behind the skills — refreshed by larapilot:update, which also removes the runtime files a new version no longer ships |
cache/ |
The runtime compiled for the settings of the project, and what each conversation already loaded — derived, never committed (it ignores itself) |
auth.yaml |
Dashboard users — password hashes, roles, authenticator secrets encrypted with APP_KEY — and the hash of the API token. Git-ignored |
auth-audit.log |
One line per sign-in, failure, second factor, user change, token, anonymization map, and file-manager zip — never a password, a code, or a token. Git-ignored |
anonymization.yaml |
What each column of the database becomes in an anonymized copy, and the Faker locale — committed |
roadmap.yaml · roadmap-link.yaml |
The public wording of the roadmap (committed) · the token of this host's public link (git-ignored) |
aikido.yaml · hackly.yaml |
Decisions about the findings of the security scanner and the repository chosen — committed, never a credential |
techdocs/ |
Generated Backstage TechDocs (after larapilot:backstage-export --write) |
Design systems
/larapilot-design draws every mockup in a design system: one of the 15 packaged in .larapilot/design-systems/, one built for the client from an interview, or the design system of an adopted app as it is. When you pick several, each becomes a style of the same screens on /larapilot/design, and Use this style keeps one. The frontend is then built to mirror the chosen mockups.
| Kind | Systems |
|---|---|
| Laravel panels and kits — they look like the real product | Filament 4, Laravel Nova 5, the Laravel Starter Kits (React, Vue, Svelte, and the Livewire kit's Flux look), shadcn/ui |
| CSS frameworks — real class names you can copy | Bootstrap 5, AdminLTE 4, Tailwind CSS 4, Material Design 3 |
| Enterprise — backoffice and product site | Fluent 2 — the look of Microsoft 365 and the Azure portal, with Fluent's own token names |
| Aesthetic — backoffice and public site | Nordic SaaS (the Nordic minimal default), Modern (Linear / Vercel-like, dark first), Bold California (sunny, heavy type, pill buttons), Berlin (Swiss grid, monochrome and one acid signal colour), New York (Didone headlines on newsprint, subway signage, taxi yellow), Zen (black and white only, empty space, hairlines) |
Every system has the same shape: a README with the mapping to real code, components.md, sources.md with what was verified and what was assumed, one tokens.css, and static screens in html/ — at least a dashboard, a resource list, a resource form, sign-in, settings, the components, and an empty state, in light and dark. Screens render offline: no CDN, no web font, no script. Sign-in screens draw their fields, so password managers leave them alone. Open html/index.html of any system as its catalog, or browse them on /larapilot/design. The index of every screen is .larapilot/design-systems/README.md.
Developer domain docs (docs/devs/, always on)
The one place where the reasoning behind the implementation survives the session that produced it. /larapilot-implement writes a Markdown file per domain / entity / feature — billing.md, user-authentication.md, webhook-ingestion.md — with a fixed skeleton:
| Section | What it carries |
|---|---|
Purpose |
What the domain is responsible for, in business terms |
Functional flow |
How it behaves step by step at runtime, 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 |
Key decisions & invariants |
The rules that must stay true, and what breaks when they do not |
Extension points & gotchas |
Where to plug new behavior in, and the traps |
Three rules make it useful instead of decorative:
- Always English, whatever language the PRD and the conversation use — these files address whoever inherits the codebase.
- Updated in the same spec that changes the behavior, inside the task commit. A task is not
task-donewhile its domain doc describes the old code, and a domain whose code moved without its doc is a High review finding. - Never deferred. Domain docs survive
effort: ECO— the prose gets terse, the file still gets written.
There is no setting to turn them on: the folder ships with its contract (README.md) and skeleton (TEMPLATE.md), and /larapilot-ship blocks on a stale one. Path key: paths.dev_docs. Full contract: .larapilot/runtime-dev-docs.md.
A project with no docs is brought level on the first change, not gradually. config-show reports data.dev_docs.documented; when it is false and the codebase already has domains, the first spec, fix, or hotfix documents every existing domain, commits the backfill on its own (docs(US-XXX): bring developer domain docs level), and only then runs its own work. /larapilot-adopt does the same at the end of onboarding. Where the original reasoning is unrecoverable, the file says <!-- TODO: verify --> rather than inventing a motive.
This is not the handbook (docs/handbook/) — that optional manual is a mixed technical/functional manual for the whole project. docs/devs/ is engineering-only and mandatory.
Project settings
Set with /larapilot-settings or php artisan larapilot:settings-set --{setting}=VALUE (for example --effort=ECO); read with php artisan larapilot:config-show --only=settings.
| Group | Keys → default |
|---|---|
| Process | effort → STANDARD (ECO · MAX) · backlog → STANDARD (LEAN · GRANULAR) · git_mode → GITFLOW (NO_GITFLOW · GITFLOW_PUSH) · testing → NORMAL (MINIMAL · BEST) · quality_scope → ALL (CHANGED: an existing codebase) · auto_approve → NO |
| Tracking | lucille → YES (switched off by ECO unless you pass --lucille=YES) · decision_log → YES · code_history → NO |
| Discovery | prior_art → YES (Sebastian's existing-solutions search at inception, consent asked before every search) |
| Business | account → NONE (FREELANCE · COMPANY unlock Economics) |
| Delivery extras | release_mode → NO · project_docs → NO · sprints → NO (two-week sprints on the Board, the Plan, and in planning) · public_roadmap → NO (a public link to the roadmap) |
| Automation | hooks → NO (workflow hooks from .larapilot/hooks.yaml) |
| Access & security | comments → NO · api_auth → NO (the API token is required on a developer's machine too; elsewhere it always is) · security_scan → NO · security → NO with security_provider aikido · hackly (security scanners) · errors → NO with errors_provider (production errors) |
| Integrations | github · gitlab · bitbucket · azure · notifications · notify_slack · notify_discord · notify_telegram → all NO |
php artisan larapilot:settings-set --effort=ECO --testing=MINIMAL
php artisan larapilot:settings-set --release-mode=YES --comments=YES
php artisan larapilot:settings-set --sprints=YES --public-roadmap=YES --security-provider=hackly
There is no dashboard_auth setting any more: who gets into the dashboard follows APP_ENV — Dashboard sign-in & roles. A dashboard_auth line left in config.yaml is ignored, and --dashboard-auth is no longer a flag.
ECO never spawns sub-agents and defers docs theater (README, PDFs, diagrams) — but still updates OpenAPI when an API changes, and still writes the developer domain docs. Playwright/E2E only runs under testing=BEST.
Two configuration layers
| Layer | File | Owns | Changed via |
|---|---|---|---|
| Laravel config | config/larapilot.php (publishable) + .env |
Environment toggles: routes, diagnostics, LARAPILOT_API_TOKEN, webhooks and tokens, Backstage and tracker credentials, package defaults |
php artisan vendor:publish --tag=larapilot-config, env vars |
| Project workflow | .larapilot/config.yaml (committed) |
Per-project settings, paths, statuses |
/larapilot-settings or larapilot:settings-set |
The YAML wins for workflow settings; Laravel config only provides the defaults at install. Machine-specific paths (like an external frontend repo) live in .env, never in committed YAML.
A few switches live in .env alone, because they decide what the package registers before any YAML is read: LARAPILOT_ENABLED=false keeps the package to its Artisan commands (no routes, no views, no MCP server, no recorders); LARAPILOT_DASHBOARD_ROUTE=false and LARAPILOT_MOCKUPS_ROUTE=false leave the dashboard and the mockups unregistered; LARAPILOT_FORCE_IN_PROD=true serves the dashboard, the mockups, and the API in production, behind a password and an authenticator for every user; LARAPILOT_API_AUDIT=false stops the audit log of the API (LARAPILOT_API_AUDIT_FILE moves it). Every other variable is documented beside its key in config/larapilot.php, and the ones of the integrations in .larapilot/integrations.md after install.
Dashboard & API (dev/staging)
Larapilot · Dashboard at /larapilot. Who gets in follows APP_ENV alone: open on a developer's machine (local, testing), a sign-in page on every other environment (staging, qa, preview, development, …), and not served in production unless LARAPILOT_FORCE_IN_PROD=true — then every user signs in with a password and an authenticator app. Each user has a role, and sees the pages of that role: Dashboard sign-in & roles. The mockups at /mockups and their assets sit behind the same sign-in, for every role that sees Design.
The sidebar groups the pages as Workflow (Board, PRD, Inception, Plan, Versions, Roadmap, Design), Workspace (About, Settings, Skills, File manager, Database, Logs, Laravel, Git), Insights (Forecast, Usage, Economics, Security, SBOM, Errors), Reference (API, Docs), and Access (Users, API token, Anonymization — admins). Its foot shows the signed-in user, who opens Account, and Sign out; the version number links to larapilot.web.ap.it.
| Page | URL | What you see |
|---|---|---|
| Board | /larapilot |
Kanban by status, with search and priority / epic / status filters; Group by: Status · Version · Sprint and the version scope — one version, several, or the stories with none (versions); counts and metrics follow the cards on screen. Download status (.md) saves the board as it stands, filters included; Download epics (.md) saves the same stories as an outline — the project, each epic with its story points, its user stories with theirs, and the tasks of each with their hours |
| PRD | /larapilot/prd |
Rendered PRD with a search that looks in the PRD and nowhere else, decision journal timeline, Download PRD (.md), and a functional analysis summary download (one Markdown file in the PRD language, requirements numbered by priority) |
| Inception | /larapilot/inception |
Discovery choices snapshot |
| Versions | /larapilot/versions |
Every version with its progress, its forecast against its milestone, and the open stories no version claims; one page per version with its stories, epics, and release notes — Versions |
| Roadmap | /larapilot/roadmap |
The versions of the release ledger as Shipped · Now · Next · Later, with their stories in public words, and the public link when public_roadmap is on — Roadmap |
| Plan | /larapilot/plan |
Epics — or Group by: Version · Sprint, and the version scope — milestones, schedule criticality, and the delivery forecast: a dependency-aware Gantt with open work queued from today, one spec at a time; a milestone that names a release is done once that release is shipped. Re-planned with /larapilot-schedule. Download plan (.md) saves the epics, every story with its status, priority, points, release, blockers, and forecast window, the tasks of each planned story, the 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 compare with Use this style, zip download |
| Settings | /larapilot/settings |
Every project mode with its options explained |
| Skills | /larapilot/skills |
Every skill the agents of the project can run, whoever brought it — the project, Larapilot, another package, Laravel Boost, a hand that dropped it into the folder of an agent — with which agent has it. Click one to read it. Under them, what the agents are told: CLAUDE.md, AGENTS.md, and the rules around them, one part for each author |
| File manager | /larapilot/files |
Admins only. The five material folders — brand/, client-materials/, design-systems/, legacy/, skills/ — each with what it is for. Browse the tree, preview, read a PDF in the page, download, upload files or a whole folder (structure kept), rename, delete. A sixth folder, Project, shows the application itself, read only. Download zip on every folder |
| Database | /larapilot/database |
The tables and views of the database in .env, whatever the driver — MySQL, MariaDB, PostgreSQL, SQLite, SQL Server. Rows a page at a time with sort, search, and foreign keys that lead to the row they point at; the structure of each table; a diagram of the tables and the foreign keys between them; the migrations that ran and the ones still to run; Download SQL for a dump of the whole database, and Other formats for its structure alone, for Laravel migrations, and for seeders with its rows — the dump and the seeders with Anonymize personal data. Read only; passwords and tokens are never shown. Admins and developers; a developer in production sees the rows anonymized only |
| 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 — database, cache, session, queue, mail, filesystem, broadcasting, logging — and what is cached: configuration, routes, events, compiled views, the application cache. 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 |
12-month contribution heatmap, every branch measured against the branch it is heading for, and the history drawn as a graph with each commit on the branch it was made on. Filterable by developer |
| Forecast | /larapilot/forecast |
The story points of the backlog, the two-week sprints one person needs, and the same work with Larapilot, by epic — Download report.md — Forecast |
| Usage | /larapilot/usage |
Lucille's token and hour ledger + Markdown report, and Estimate vs build: every delivered spec with the hours it was estimated at, the time it spent IN PROGRESS, the wait in review, how many times it was sent back, and its tokens |
| Security | /larapilot/security · /larapilot/security/checkpoint |
Two tabs. The first is the scanner the project associates — Aikido or Hackly: what it found in the repository (with Hackly, in the domains linked to it too), the most severe first, with what was decided about each finding and the verdict of the ship gate. Register for the client (.md) downloads every finding — open, resolved, ignored with its reason. When the git remote matches no repository of the scanner, the page asks which one the project is. Always in the menu; with no scanner associated it describes both and how to associate either. Checkpoint: the last scan of andreapollastri/checkpoint — verdict, checks by area (dependencies, configuration, code), every finding with its suppression hash, the trend of the scans — with Run the scan and Download report (.md); when the package is missing it says how to install it |
| SBOM | /larapilot/sbom |
Every package the project ships — Composer, the JavaScript of the repository, and the frontend companion — from the lockfiles: version, direct or transitive, production or development, license (copyleft flagged), abandoned packages. Check vulnerabilities asks OSV.dev; the vulnerable packages come grouped with the version that fixes them and the command to run, and what was decided about each. Downloads: SBOM (.md), CycloneDX (.json), Vulnerabilities (.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, and what was decided about it — day by day when the tracker records every throw. Always in the menu; with errors off it says what Boogle is, how to connect it, and which other trackers can be read instead |
| Economics | /larapilot/economics |
What the project costs, what the client pays, what is left for you — every sum written as a receipt (account ≠ NONE) |
| Spec | /larapilot/specs/{code} |
Story, plan, tasks, mockups, decisions, internal feedback. The story keeps its lines as written, and its **Status:** follows the backlog. Download spec (.md) saves all of it, tasks included, in one file |
| API docs | /larapilot/api/docs |
Swagger UI over the JSON API |
| Docs | /larapilot/docs |
Delivery loop, packaged skills, persona roster |
| Account | /larapilot/account |
Your password, and your authenticator turned on or off — every signed-in user |
| Users | /larapilot/users |
Admins. Create users with a role, change roles and passwords, rename, reset a lost authenticator, remove |
| API token | /larapilot/api-token |
Admins. Generate, rotate, or revoke the token of the JSON API — shown once |
| Anonymization | /larapilot/anonymization |
Admins. What every column of the database becomes in an anonymized copy — Anonymization |
| About | /larapilot/about |
What the project runs on: Laravel, PHP, the database server, Node — each with its upstream support window drawn as a bar (bug fixes, security fixes, today) and an alert when it is past or near its end — the project, PHP extensions and limits, connections, drivers, the packages that shape an upgrade (Filament, Nova, Livewire, Inertia, …), frontend, CI and deploy, and every file that pins a version. The host, port, and name of the database are shown to admins only |
The dashboard follows the system theme; pin light or dark from the sidebar. Every page works on a phone. A page your role does not see is not in your menu, and answers 403 with the reason.
| Plan | Spec | Database |
|---|---|---|
![]() |
![]() |
![]() |
| Logs | Laravel | Git |
![]() |
![]() |
![]() |
Taken on Fernway, the demo app of the documentation — more on the tour.
Dashboard sign-in & roles
There is no dashboard_auth setting and no HTTP Basic Auth. The dashboard has its own sign-in page, and what it asks follows APP_ENV:
APP_ENV |
What the dashboard does |
|---|---|
local, testing |
Open. No sign-in: whoever reaches it acts as an admin |
any other but production — staging, qa, preview, development, … |
A sign-in page: username and password. An authenticator app is optional — each user turns it on from Account |
production |
Not served (404), unless LARAPILOT_FORCE_IN_PROD=true. Then every user signs in with a password and an authenticator app, and enrols one at the first sign-in if they have none |
| Sign-in | Users | Account |
|---|---|---|
![]() |
![]() |
![]() |
There is no sign-up and no password reset. An admin creates the users and sets their passwords, on the Users page or from the shell. The first admin of a server is always made from the shell: until a user exists, a shared host answers HTTP 503 with the command to run.
php artisan larapilot:dashboard-user add andrea --role=admin # asks for the password (12+ characters)
php artisan larapilot:dashboard-user add giulia --role=pm --password='…'
php artisan larapilot:dashboard-user role giulia dev # admin · dev · pm · ext
php artisan larapilot:dashboard-user password giulia # a new password; her open sessions end
php artisan larapilot:dashboard-user rename giulia giulia.rossi
php artisan larapilot:dashboard-user totp-reset giulia # a lost phone: she enrols again
php artisan larapilot:dashboard-user remove giulia
php artisan larapilot:dashboard-user list
--role defaults to admin. Users live in .larapilot/auth.yaml — password hashes (bcrypt or argon2id), roles, and the authenticator secrets encrypted with APP_KEY — git-ignored, with no database table and no User model. Each server has its own users.
| Role | Sees |
|---|---|
admin |
Everything: every page, Users, API token, Anonymization, the file manager, and where the database lives |
dev |
Everything but Users, API token, Anonymization, and the file manager. Never the host, port, or name of the database. In production, the Database page and every download from it are anonymized, always |
pm |
Workflow (Board, PRD, Inception, Plan, Versions, Roadmap, Design), Insights (Forecast, Usage, Economics, Security, SBOM, Errors), and Reference (API, Docs). Not the Workspace — but the Logs outside production |
ext |
Workflow only: Board, PRD, Inception, Plan, Versions, Roadmap, Design and its mockups — for a client or an outside stakeholder |
Every role has Account. The menu shows each role its pages; any other page answers 403 and says why.
- Users (admins) creates users with a role, changes roles and passwords, renames, resets a lost authenticator, and removes users. An admin never acts on themself there — another admin does — and can act on every other admin. Account is every user's own: their password, and their authenticator turned on or off.
- A change takes effect at once. A new role, a new password, or a removed user applies on the next request of every open session.
- The authenticator is any TOTP app (RFC 6238) — Google Authenticator, Microsoft Authenticator, 1Password, Authy. It is enrolled from a QR code drawn on the server; the secret is kept encrypted with
APP_KEYand never leaves the server. A code is good once, thirty seconds either side of the clock. The app shows the account underLARAPILOT_DASHBOARD_AUTH_ISSUER(defaultAPP_NAME). - Passwords have at least twelve characters and are not the username. A username is letters, digits, and
. _ @ + -, up to 64 characters. - Throttling. Wrong passwords are limited per address —
LARAPILOT_DASHBOARD_AUTH_MAX_ATTEMPTS, 20 a minute — and per name, 5 a minute from anywhere; wrong codes per name. Behind a load balancer or a CDN, have the application trust the proxy (TrustProxies), or every visitor shares one address. - Sessions. The sign-in and sign-out are forms with CSRF; the session is regenerated at every sign-in.
- Audit trail.
.larapilot/auth-audit.log, git-ignored, keeps one JSON line per sign-in, failure, second factor, user change, API token, anonymization map, and file-manager zip — never a password, a code, or a token.LARAPILOT_DASHBOARD_AUTH_AUDIT=falseturns it off;LARAPILOT_DASHBOARD_AUTH_AUDIT_FILEmoves it. - From 5.0. An
auth.yamlwritten before 5.1 is read as it is, every user an admin, andlarapilot:updaterewrites it with roles and says whom it changed. - In production the package must be installed on the server: a deploy with
composer install --no-devleaves out a Larapilot required with--dev. Serve the dashboard over HTTPS on every shared host.
File manager
/larapilot/files manages what you hand the skills before they start. It is for admins only, on every environment — on a developer's machine, where the dashboard is open, that is whoever uses it:
| Folder | What it is for |
|---|---|
brand/ |
Logo, palette, typography, and the brand guide |
client-materials/ |
Briefs, analyses, and documents supplied by the client |
design-systems/ |
Visual references and tokens the mockups are built on |
legacy/ |
Snapshots of the old system to port or migrate |
skills/ |
Your custom skills, one folder per slash command |
./ — Project |
The Laravel application itself: code, config, routes, tests. Read only |
- Adding, renaming, and deleting files and folders work in the five material folders. Project is read only: you browse, preview, and download; the routes that write do not know the folder and the service refuses a write to it.
- 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/andstorage/framework/: a cached configuration holds every value of.env. Files that start with a dot are shown.vendor/andnode_modules/can be opened and are left out of the count. - Credentials show their keys, never their values.
auth.json,.npmrc,.yarnrc,.netrc,.pgpassare shown with every value replaced by*****************, on screen and in the download..envand.env.*are shown as they are outsideproduction, and to an admin inproduction; masked for anyone else. A template such as.env.exampleis shown as it is. A key or certificate is one line of asterisks; a database (.sqlite,.db) is listed and neither shown nor downloaded. A.logfile is shown and downloaded with the passwords, tokens, and keys it quotes replaced by[REDACTED], as on the Logs page — which reads a log whole, by entries, where the file manager shows its first 256 KB. - A PDF is read in the page: one page or two side by side, zoom, page jump, full screen. The reader is PDF.js from cdnjs, checked against its hash; where it cannot load, the file opens in its own tab.
- Download zip on every folder, the root of each included: what the folder shows, at any depth. Databases are left out, keys are their mask, logs are redacted, and the folders a listing hides stay out — dot folders and the caches of the framework in Project.
vendor/andnode_modules/are zipped only when you open them and zip them themselves, never with a parent. The archive stops atLARAPILOT_FILE_MANAGER_MAX_ZIP_MB(default 512) and says so. Every zip is written to the audit trail. - Upload a folder and it keeps its structure; an existing file is kept unless Replace existing files is on. One file is limited by
LARAPILOT_FILE_MANAGER_MAX_UPLOAD_KB(default 50 MB) and by PHP'supload_max_filesize/post_max_size, whichever is lower. - Behind the sign-in and the role. Client documents, legacy snapshots, and the files of the project are an admin's: a developer, a product manager, or an external user does not see the page, and its addresses answer 403.
- Nothing outside the six folders can be reached, a symlink is never followed, and an uploaded
.html,.js, or.svgis never run in the dashboard.LARAPILOT_FILE_MANAGER=falseremoves the page.
Database
/larapilot/database shows the application's own database — the connection named by DB_CONNECTION, with the DB_* values in .env — through Laravel's schema and query builders, so the page is the same on MySQL, MariaDB, PostgreSQL, SQLite, and SQL Server. It reads, and never writes.
- The list: every table and view, with its size where the driver reports one, and the driver, database, and host on top — never the password; a developer sees the driver alone, and never the host, port, or name of the database, not even in the message of a failed connection. On MySQL/MariaDB only the database in
.envis listed; on PostgreSQL every schema is, a table outsidepublicnamedschema.table. A connectionprefixis left out of the names. - Rows, 50 to a page (
LARAPILOT_DATABASE_VIEWER_PER_PAGE), by primary key. Click a column to sort; the search looks in the text columns, without regard to case; a foreign key value links to the row it points at. Click a row to open it whole — long text in full, JSON indented, Copy as JSON, and Copy as SQL INSERT: the row as oneINSERTstatement of the driver, its columns named, ready to run on a database with the same table. Binary is shown as hex with its size. - Structure: columns (type, null, default, primary key, auto increment, comment), indexes, foreign keys with their on update / on delete, and the create statement — the
CREATE TABLE(orCREATE VIEW) that builds it, with its indexes and keys, in the SQL of the driver, to copy. - Diagram, 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; the ones no foreign key touches are in rows below. Keys only leaves the keys and counts the rest. Click a table to keep only its relations lit; its name opens it.−,+, Fit, Ctrl/⌘ with the wheel, and a drag move around it. Drawn on the server as plain SVG — no library, nothing fetched — for up to 300 tables and views; the structure alone, never a row. Download PDF saves the diagram as it is shown — all columns or keys only — on one page as large as the drawing, in vectors, with the database, the counts, and the day on top (/larapilot/database-diagram.pdf). - Migrations, the third view (
/larapilot/database?view=migrations): which migrations ran and which are still to run, asphp artisan migrate:statustells it — the files of the application and of its packages against themigrationstable. The pending ones come first, then the ones that ran with their batch, the newest first; a migration that ran from a file no longer there is marked Ran · file gone. Before the firstmigrateevery file is pending. Nothing is run from the dashboard. - Credentials are never shown. A column named like a password, token, or secret (
*password*,remember_token,token,*_token,*secret*,two_factor_recovery_codes,*api_key*,*private_key*) is shown as*****************and is never searched, sorted, or filtered on. Add patterns indatabase_viewer.masked_columnsofconfig/larapilot.php. - Download SQL writes the whole database as one
.sqlfile in the dialect of its driver — structure, every row, then indexes, keys, sequences, and views — to restore withmysql,psql -f,sqlite3, orsqlcmd. MySQL, MariaDB, and SQLite give their ownCREATEstatements; PostgreSQL is rebuilt from its catalogs likepg_dump(schemas, enum types, serial and identity columns with their next value); SQL Server from Laravel's schema builder. Each table and view is dropped first if it exists. It is written while it downloads, from one read-only snapshot. - The dump leaves credentials out: the hidden columns are written as
NULL, or''whereNULLis not allowed —hidden-1,hidden-2, … where a unique index holds the column, so the file still restores — and listed at the top of the file. Include passwords and tokens puts them in — offered only inlocalandtesting. The same holds for the seeders and for Copy as SQL INSERT, which never carries them. - Anonymize personal data, beside Download SQL and Laravel seeders, writes every row through the anonymization map: names, emails, phones, addresses, and free text replaced, keys and record dates kept, so the copy still imports. The migrations are the structure alone and have no row to replace. A developer in
productionalways gets the anonymized rows — on screen and in every download — and cannot turn it off; search and filters are off while the rows are anonymized, so a match never tells a value. An admin sees the real rows and ticks the box for an anonymized download. - Other formats, beside Download SQL:
- 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
.zipto unpack in the root of a project: a file indatabase/migrationsfor 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 above it. Themigrationstable is left out. - Laravel seeders — a
.zipwith a class indatabase/seedersfor each table that holds rows, andDatabaseDataSeeder, which calls them in the order of the migrations:php artisan db:seed --class=DatabaseDataSeederon tables that are there and empty. Bytes are kept through base64, the next id is set on PostgreSQL, 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— no extension, since a web server may keep.sqland.zipfor itself./larapilot/database.sqlstill answers. - Admins and developers see the page, wherever the dashboard is served.
LARAPILOT_DATABASE_VIEWER_CONNECTIONreads another connection;LARAPILOT_DATABASE_VIEWER=falseremoves the page.
Anonymization
/larapilot/anonymization (admins) is the map of what the personal data of the database becomes in an anonymized copy: on the Database page for a developer in production, and in every SQL dump or seeder downloaded with Anonymize personal data. It lists every column of every table with what it becomes and an example beside it.
| A column can be | What it writes |
|---|---|
| Keep | The value as it is |
| Hide | NULL, or empty where NULL is not allowed |
| Same shape | New characters in the same pattern — letters for letters, digits for digits, the separators kept |
| A Faker formatter | Any formatter of fakerphp.org, grouped as the site groups them: Person (name, first and last name, title, date of birth), Address, Phone, Company, Internet (email, username, URL, IP, user agent), Text, Date and time, Payment (IBAN, SWIFT/BIC, card number), Identifiers (UUID, EAN-13, ISBN-13), Miscellaneous |
- The Faker locale is chosen among every locale the installed Faker ships — 73 in Faker 1.24 — and writes names, addresses, and phones of that country. A locale adds formatters of its own — an Italian codice fiscale, a French NIR, a Spanish DNI — listed apart.
- Safe defaults. A column with no line of its own follows a default that errs on the side of privacy: a name, an email, a phone, an address, an identifier, free text are replaced; keys, record dates, statuses, enumerations, and flags are kept, so the copy still imports and works. The tables of the framework are known by name:
migrationsand cache locks kept, queue payloads, failed-job exceptions, cache values, and session payloads hidden, the address and browser of a session replaced. - Passwords, tokens, and secrets stay hidden whatever the map says.
- The same value always becomes the same replacement on one install — seeded with an HMAC of the value keyed with
APP_KEY— so a foreign key written as text still matches and relations survive. No hash of a real value is ever written. - A replacement fits its column: its type and its length.
- The map is
.larapilot/anonymization.yaml, committed, so every environment anonymizes the same way. Saving it, and changing the locale, go to the audit trail.
Logs
/larapilot/logs reads what the application wrote to storage/logs — every .log file, the one Laravel writes to now opened first, or the newest when it writes to none of them. 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.
- 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. 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. 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
Authorizationheaders are[REDACTED]on screen, and a search never finds one. In a context written as JSON the value under such a key 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 inlocalandtesting. - Admins and developers see the page wherever the dashboard is served; product managers too, outside
production.LARAPILOT_LOG_VIEWER_PATHreads another folder — absolute, or from the root of the project;LARAPILOT_LOG_VIEWER=falseremoves the page. Only the.logfiles of that folder are opened, three folders deep, and a symlink is never followed. - For the skills,
php artisan larapilot:logsreads the same files — see Diagnostics and logs./larapilot-bugand/larapilot-errorrun it every time.
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.
- 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
synca job runs inside the request, withloga 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.envis not read until it is cleared. - Schedule. The tasks of the scheduler as
php artisan schedule:listprints them, read fromroutes/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 callsschedule: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 thedatabaseandredisdrivers — 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 thequeue:retrycommand 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,
logandarrayincluded, and changes nothing of the mail or of where it goes. - Dumps. What
dump()anddd()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 instorage/larapilot/, a folder that ignores itself in git (LARAPILOT_LARAVEL_VIEWER_PATHmoves it). Forget them all empties a list. They are kept on a developer's own machine only —localanddevelopment, 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=trueorLARAPILOT_LARAVEL_VIEWER_DUMPS=truekeeps them wherever the page is served;falsenever. - Admins and developers see the page, wherever the dashboard is served.
LARAPILOT_LARAVEL_VIEWER=falseremoves the page.
Roadmap
/larapilot/roadmap, in the Workflow menu and seen by every role, is the product roadmap: the versions of the release ledger as Shipped · Now · Next · Later — shipped, in progress, the next planned release, the ones after it — each with its stories in public words and its target. It is built from .larapilot/releases.yaml, the ledger release_mode keeps: with no release in it, the roadmap is empty.
| Roadmap | The public link |
|---|---|
![]() |
![]() |
- The public link. With
public_roadmapon (php artisan larapilot:settings-set --public-roadmap=YES), the page shows a link to send outside:/larapilot/roadmap/p/{token}. It shows the same lanes and leaves out spec and epic codes, people, hours, points, costs and tokens, branches, tags and commits, comments and blockers, mockups, and the stories a release does not claim or that are hidden. The token is unguessable and regenerated with one click — the old link stops at once. It is a secret of each host, kept in.larapilot/roadmap-link.yaml, out of Git. The link answers even inproduction, where the dashboard is hidden, as long as the setting is on and the package is installed there; it is throttled to 60 requests a minute. - Public wording.
.larapilot/roadmap.yaml, committed, keeps what the public reads in place of the ledger: an intro, a title, a one-line summary, and a target for a version; a title and a summary for a story; what is hidden.
php artisan larapilot:roadmap-set --intro="What we are building for our members."
php artisan larapilot:roadmap-set --semver=0.2.0 --title="Waitlist" --summary="Join a full class and get the seat when one frees up." --target="Q1 2027"
php artisan larapilot:roadmap-set --semver=0.2.0 --item=US-014 --title="Book from the calendar"
php artisan larapilot:roadmap-set --semver=0.2.0 --item=US-019 --hide # --show puts it back
php artisan larapilot:roadmap-show # what the public sees
--target takes a quarter, a month, or YYYY-MM-DD, for a version only; an empty value restores the ledger's title or clears the field. /larapilot-release runs roadmap-show after a release changes, and words what reads as internal.
Versions
With release_mode on, the version is a dimension of the whole product: of every page that reads the backlog, and of every step of the workflow.
- The Versions page —
/larapilot/versions, in Workflow, for every role. Each version as a card, the open ones first: status, stories done out of all and the story points, a bar of what is done and in progress, the day its last open story is forecast to land on the Plan, its milestone with Late or Overdue when the forecast misses it, its release branch (admins and developers), and links that open the Board, the Plan, and the Forecast on that version. Above them: the version the work is on, the next milestone, and the open stories no version claims, listed. A version opens on its own page —/larapilot/versions/1.2.0— with its stories in the order they are picked up, its epics, what it moved out when it shipped, and its release notes, with Release notes (.md). - The version scope — on the Board, the Plan, the Forecast, and the Versions page, one chip for each version and one for No version: a click adds or removes it, so the page shows one version, several, or the stories with none. The scope stays while you move between those pages, until All versions clears it, and it travels in the address (
?version=1.2.0,1.3.0,?version=none,?version=all). The Board's figures, its downloads, the Plan's Gantt and milestones, and the Forecast's figures andreport.mdfollow it. - Forecast by version — the Forecast has a By version table: story points, the hours left for one person and with Larapilot, the sprints left, the forecast day, and the milestone of each version. The rate a point takes with Larapilot is measured on the whole backlog, even when the page is scoped.
- On a spec — the spec page shows the version it targets, linked to the version.
- Group by on the Board — Status · Version · Sprint — and on the Plan — Epic · Version · Sprint. Version is offered when
release_modeis on or the ledger holds releases; a version column links to its page and shows its progress. The version of a spec is the release that claims it, or its**Release:**line. - In the skills — every skill names the version it works in (
v1.2.0 3/5 (60%)). Inception and adopt leave no story without a version; feature, spec, and bug give each new story one — a fix of a shipped version goes in its next patch;spec-nextpicks the version the work is on first; implement stops on a story with no version and asks; review closes with how far the version is, and says when it delivered its last story; autopilot works one version at a time and stops at its end; ship refuses a version with open stories until they are delivered, moved (release-ship --defer-to=1.3.0or--defer-to=backlog), or shipped anyway (--force), and keeps the release notes before the ceremony. The rules are in.larapilot/runtime-versions.md. - From the shell —
release-show [--semver=](one version: stories, open ones, forecast, risk),release-notes [--semver=] [--file=] [--write](the notes, drafted from the stories — Added, Changed, Fixed, Not delivered yet, Moved — or kept in.larapilot/docs/releases/vX.Y.Z.md),release-listwith each version'sprogress,spec-list --release=1.2.0|none,spec-next --release=, andrelease-set --add-spec=that moves a story out of any other open version (--remove-spec=drops one).larapilot:contextgives every skillrelease: the version the work is on, each open version's progress, the stories with none. - API —
GET /larapilot/api/versionsand/versions/{version}, and?version=on/specs.
Sprints on the Board and the Plan
- Sprints (
php artisan larapilot:settings-set --sprints=YES) split the work into two-week sprints of one person. A spec carries the sprint someone decided —php artisan larapilot:spec-sprint US-012 3,noneto clear it — or, until someone does, the sprint it is proposed for: the open specs laid one after another in delivery order into what one person does in a sprint (the sprint hours over the hours a point stands for, as on Forecast). Sprint 1 starts on the first day the ledger recorded work. A proposed sprint is marked proposed. - The API returns the release and the sprint of each spec:
release,sprint, andsprint_proposedon/specsand/specs/{code}.
Forecast
/larapilot/forecast, in Insights before Usage (admins, developers, product managers), answers how long the backlog takes — by a person, and with Larapilot. Every number says where it comes from.
| Figure | How it is computed |
|---|---|
| Story points | The points of the backlog: done, under way (IN PROGRESS and REVIEW), to do. A spec with no points is counted apart |
| Hours a point stands for | The rate the plans imply — task hours over points — once at least two specs and five points are planned; until then the effort setting: 3 (ECO), 4 (STANDARD), 5.5 (MAX) |
| A sprint | Two weeks of five days of one person: 60 hours with the six-hour delivery day, or the day of the Economics profile when one is set |
| By a person | Points × hours per point; sprints = those hours ÷ the hours of a sprint, the last one started counted whole |
| With Larapilot, measured | Once delivered specs carry hours in Lucille's ledger: the hours they took ÷ their points, applied to the backlog. Called a first reading under three specs |
| With Larapilot, modelled | Before that, a range: the build work of a person ÷ 10 (optimistic) or ÷ 5 (realistic), plus 0.5 or 1 hour of a person per spec for reading the plan, reviewing, and approving |
The same figures come by epic, and Download report.md saves them with their assumptions.
JSON API
| Endpoint | Returns |
|---|---|
GET /larapilot/api/board |
Metrics and specs grouped by status |
GET /larapilot/api/specs · /specs/{code} |
Specs with task progress, mockups, feedback, release, and sprint (paginated, ?status=, ?version=1.2.0,none) |
GET /larapilot/api/versions · /versions/{version} |
Every version with its progress, forecast, milestone, and risk; one version with its stories, epics, and release notes |
POST /larapilot/api/specs/{code}/comments |
Append internal feedback (comments=YES) |
GET /larapilot/api/prd |
PRD Markdown and heading index |
GET /larapilot/api/metrics |
Delivery snapshot + effort timing + build: the estimates of the delivered specs beside the time they took |
GET /larapilot/api/economics |
Economics snapshot; what-if parameters (?hourly_rate=70&discount_pct=10&tier=premium) are computed, never stored |
GET /larapilot/api/diagnostics |
Read-only runtime snapshot for bug triage |
GET /larapilot/api/backstage · /backstage/catalog-info.yaml |
Backstage entities and delivery snapshot |
GET /larapilot/api/openapi.json · /docs |
OpenAPI 3 document and Swagger UI |
The API token. An admin makes it on the API token page (Access group) or from the shell; it is shown once, and only its hash is kept in .larapilot/auth.yaml. Rotating it ends the old one at once. A token in .env (LARAPILOT_API_TOKEN=…) works too. Send it on every request as Authorization: Bearer <token> or X-Larapilot-Token: <token>.
php artisan larapilot:api-token generate # show · generate · rotate · revoke
| Where | No token exists | A token exists |
|---|---|---|
local / testing, api_auth=NO (default) |
Open | Every request needs it, reads included |
local / testing, api_auth=YES |
HTTP 503, with how to make one | Every request needs it |
| any other environment | HTTP 503, with how to make one | Every request needs it |
production |
Not served (404), unless LARAPILOT_FORCE_IN_PROD=true; then as above |
api_auth matters on a developer's machine only: elsewhere the token is always required. The dashboard sign-in does not close the API: on local with no token, anyone who reaches the port reads it. Make a token, or turn api_auth on, whenever the machine is reachable from others — a Sail port on every interface included. With the API open, a write (POST …/comments) is accepted only as JSON and not from another site (Content-Type: application/json, no cross-site Origin or Sec-Fetch-Site): a web page in your browser cannot post a comment the agent would then read. A wrong or missing token is HTTP 401. A user signed in on the dashboard whose role sees the API (admin, dev, pm) reads it from the browser — the Swagger UI included — without the token; never a write, the group has no CSRF. Call the API from a server, never from browser code that would carry the token.
Rate limited per IP (LARAPILOT_API_RATE_LIMIT, default 120,1), mutating requests audited to .larapilot/api-audit.log, ETag / 304 on the read endpoints. Workflow state changes only through skills and Artisan — never from the dashboard or the API.
Economics (account, NONE by default)
settings.account is NONE | FREELANCE | COMPANY. Freelance uses sole-trader regimes (Italian forfettario, IRPEF, autónomo, …); company uses corporate tax plus dividend extraction (SRL, SPA, Ltd, GmbH, C-Corp, …), FY-2026 rates across 38 countries. Either unlocks /larapilot-economics and /larapilot/economics.
php artisan larapilot:settings-set --account=FREELANCE
php artisan larapilot:economics-set --country=IT --regime=forfettario_15 --hourly-rate=55
php artisan larapilot:economics-set --product-model=saas --price-monthly=29 --churn=4
- The page is written for someone who has never read a balance sheet. 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.
- The short answer — one sentence and four figures: 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.
- 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) and where the money goes (client price − running costs − tax and contributions − accountant = what you keep).
- A subscription, one step at a time — what one customer leaves, what the product costs every month, how many customers it takes with the division written out, and a chart of when the money comes back that reads any month by pointer or keyboard and tells the same story in sentences. Three forecasts side by side, each with a verdict in words.
- Words used here — VAT, margin, net, fixed costs, churn, break-even, and the rest, each with the figure it has in this project.
- A console of dropdowns (rate, margin, discount, team size, regime, how it is sold, price line, scenario, …) recomputes everything server side. Nothing is written from the browser: a simulation prints the
economics-setcommand that would make it real. - Hours come from the backlog — planned task hours per spec, story points where no plan exists — and the snapshot refreshes itself whenever specs, plans, the PRD, or inception change.
- What the delivered work took — under the hours, one line for you alone: the delivered specs as the quote counts them, and the time the agent took to build them (the time they were
IN PROGRESS, pauses included). Your own hours on them are not in it. It is on the page and in the internal report, never in the client quote. Spec by spec: Estimate vs build on Usage. - Sold as
fixed·saas·ecommerce·packagechanges how payback is read, never the hours or the build price. Onautoit follows the Business Model answer from inception. - Market research from Jennifer and Benjamin (
larapilot:economics-market-write) is plotted, never invented; skipped on a one-off client delivery unless you ask. - The maintenance retainer is priced from the inception answers (delivery target, who runs the server, support window, ship method) from a 12% baseline, and the page shows the arithmetic.
- The client quote is written by
/larapilot-economicsin the PRD's own language (.larapilot/docs/quote.md, download at/larapilot/economics/quote.mdoreconomics-show --format=quote), with infrastructure and security chapters derived from the project's real settings. A built-in template coversen·it·es·fr·de·pt·nl·pluntil one is written.
Figures are planning estimates, not tax advice.
Traceability
Decision journal & regression guard (decision_log, ON by default)
Every explicit choice — a fixed-choice answer or a free-text directive like "the background must be orange" — is appended, with a timestamp, to .larapilot/decisions.yaml:
php artisan larapilot:decision-log --topic="background color" --value="orange" --source=chat --skill=larapilot-inception
Before a later phase records a different value for the same topic, it runs larapilot:decision-check --topic="background color" --value="red". If that contradicts an earlier decision, the skill asks you to confirm — "on 2026-05-01 you chose orange; confirm red supersedes it" — and re-logs with --supersedes=<id>. The file is never rewritten. Turn it off with --decision-log=NO.
Code change history (code_history, OFF by default)
php artisan larapilot:settings-set --code-history=YES
# after each task-done, /larapilot-implement runs:
php artisan larapilot:code-log --spec=US-014 --task=TASK-03 --skill=larapilot-implement
php artisan larapilot:code-history --file=app/Models/Post.php # where has this file been worked on?
Security
| Gate | Default | Turn it on |
|---|---|---|
Dashboard sign-in — a sign-in page, roles, and an authenticator on /larapilot and the mockups |
follows APP_ENV: open in local; a sign-in elsewhere; hidden in production |
larapilot:dashboard-user add andrea --role=admin on each shared host; LARAPILOT_FORCE_IN_PROD=true to serve it in production, password + authenticator for all |
API token — bearer token or X-Larapilot-Token on /larapilot/api/* |
required wherever a token exists; outside local the API answers 503 until one does |
the API token page, larapilot:api-token generate, or LARAPILOT_API_TOKEN |
| API auth — the token required on a developer's machine too | OFF | --api-auth=YES |
Security scan — andreapollastri/checkpoint in review and pre-ship; the last scan on Security → Checkpoint |
OFF | composer require --dev andreapollastri/checkpoint then --security-scan=YES |
| Vulnerable dependencies — every package of the SBOM against OSV.dev, at the ship gate and on the SBOM page | on demand | nothing: php artisan larapilot:vendor-audit or /larapilot-vendor-check |
| Security scanner — the findings of Aikido or Hackly for the repository, in triage and at the ship gate | OFF | credentials in .env, then --security-provider=aikido or hackly |
| Production errors — what the running application throws, read from Boogle, Sentry, Bugsnag, Flare, Datadog, Rollbar, Honeybadger, or CloudWatch, in triage and on the dashboard | OFF | a credential that reads the tracker in .env, then --errors=YES --errors-provider=… |
- The dashboard and the API have separate doors. The sign-in never opens the API to a client without the token — only the browser of a signed-in user whose role sees the API, for reads — and the token never opens the dashboard. Neither touches MCP. Details: Dashboard sign-in & roles, JSON API.
- Create the first admin before a shared host sees the dashboard. With no user, every page answers HTTP 503 and gives the command; nothing of the board, the PRD, the specs, the git history, the security findings, or the production errors is served. The mockups at
/mockupsand their assets sit behind the same sign-in and are served withX-Frame-Options: SAMEORIGIN,X-Content-Type-Options: nosniff, and aframe-ancestors 'self'policy. - The pages that read the machine follow the role. The file manager and Anonymization are an admin's; Database and Laravel an admin's and a developer's; Logs also a product manager's outside
production. A developer never sees where the database lives, and inproductionsees its rows anonymized only. Mail and dumps are recorded only inlocalanddevelopmentunlessLARAPILOT_LARAVEL_VIEWER_MAIL/_DUMPSsay otherwise. - Secrets as they are — Include passwords and tokens on a database download, Secrets as written on a log — are offered only in
localandtesting, whatever the role. - With
security_scan=YES, review and ship runlarapilot:checkpoint-scan:FAILfindings block the review (fix them, or log a waiver withlarapilot:decision-log);WARNfindings become notes. Larapilot never bundles the scanner; with the setting off it runs only when someone asks — the command, or Run the scan on the dashboard. The result stays in.larapilot/cache/checkpoint/, out of git: the details can quote code.
Checkpoint and the SBOM
php artisan larapilot:checkpoint-scan --report # runs checkpoint:scan --json, keeps it for Security → Checkpoint
php artisan larapilot:checkpoint-scan --only="Hardcoded Secrets" --gate # exit 1 when a check fails
php artisan larapilot:sbom # inventories, totals, licenses, abandoned packages
php artisan larapilot:sbom --write=both # docs/security/sbom.md and sbom.cdx.json (CycloneDX 1.5)
php artisan larapilot:vendor-audit --report --gate # OSV.dev; exit 1 on an open advisory at --fail-on=high or above
php artisan larapilot:vendor-link GHSA-xxxx-xxxx-xxxx --spec=US-012 # the spec that fixes it
php artisan larapilot:vendor-link GHSA-xxxx-xxxx-xxxx --waive --reason="Never fed user input."
- The SBOM reads
composer.lock, the JavaScript lockfile of the repository (package-lock.json,pnpm-lock.yaml,yarn.lock,bun.lock), and the one of the frontend companion when it is linked — its own, or the workspace's in a monorepo. Nothing is installed or downloaded. - The vulnerability check sends the name and the version of each package — nothing else — to OSV.dev, the open database behind the GitHub advisories, FriendsOfPHP, and npm. No account, no key. Severity is the advisory's word, else its CVSS 3 score; each package gets the version that fixes all its advisories and the command that moves to it (
composer update …,npm update …, or a new constraint when the current one does not allow the fix). - Decisions — the spec that fixes an advisory, or a waiver with its reason — live in
.larapilot/vendor-audit.yamlwith the trend of the checks: commit it. The advisories are cached in.larapilot/cache/./larapilot-shiprunsvendor-audit --gate: an open advisory athighor above that was not waived stops the release; one in the backlog counts until the update is merged.
Security scanners — Aikido or Hackly
security and security_provider, OFF by default. The project associates one scanner; it scans on its side, and Larapilot runs none and installs nothing: it reads what the scanner found, brings it into the workflow, and tells the scanner what you decided about each finding.
| Scanner | What it scans | Ids |
|---|---|---|
| Aikido — SaaS, aikido.dev | The repository, through the git provider: dependencies, code, secrets, infrastructure as code, containers | Numbers (24) |
| Hackly — self-hosted, hackly.web.ap.it | A clone of the repository (Semgrep, Trivy, Gitleaks, Composer audit, Checkpoint, Laravel checks) and the domains linked to it (TLS, headers, exposed paths, Nuclei, ZAP) | UUIDs |
php artisan larapilot:settings-set --security-provider=aikido # or hackly; none turns the findings off
php artisan larapilot:security-status
--aikido=YES is the name the setting had before 5.1 and still means --security-provider=aikido; a config.yaml with aikido: true reads as Aikido. /larapilot-security asks which scanner when none is set. The same commands work for both:
Command (php artisan larapilot:) |
What it does |
|---|---|
security-status |
Scanner, credentials, repository, last scan, hints |
security-issues (--new, --severity=, --type=, --limit=, --report, --gate, --detail={id}) |
The open findings with their state — new, in_backlog, waived, false_positive — and the verdict of the gate; --report writes docs/security/security-report.md; --gate exits 1 on FAIL; --detail gives one finding as a brief and a fix prompt |
security-plan --ids=… |
Groups the confirmed ids by kind and fix before triage — a leaked secret never merges |
security-link {ids} (--spec=US-XXX, --waive --reason="…", --false-positive --reason="…", --forget, --local) |
What was decided, kept in .larapilot/aikido.yaml or hackly.yaml (committed: ids and decisions, never a credential) and told to the scanner; --local tells it nothing |
security-push |
Tells the scanner the decisions it was not told (unsent) |
security-repos (--search=, --use={id}, --forget) |
Which repository of the scanner this project is, when the git remote finds none |
security-scan |
Asks for a new scan — Hackly: --profile=quick|standard|deep, --include-targets |
security-register |
docs/security/security-register.md for the client: every finding open, resolved, or ignored with its reason, in the language of the PRD, naming its scanner and its scale |
The aikido-* commands are the older names of the same Aikido steps and still answer.
/larapilot-securitydownloads the open findings, lets you confirm each one (resolve, waive, or skip), runssecurity-planon the ids you chose to fix, and hands each resolution group to/larapilot-triage— same kind and same fix together, secrets never merged. Triage measures it against the PRD like any request — a known vulnerability in shipped code is a bug, a requirement gap when no requirement names security — and/larapilot-bugwrites the fix spec. The link between finding and spec is recorded.- The repository is asked for when it is not found. Larapilot finds it from the git remote, by address or by name. When the code is scanned under another repository — a fork, a mirror, a different name — it does not guess:
/larapilot-securityasks which one it is,/larapilot/securityshows the list with a form, andsecurity-repos --use=…keeps the choice in the committed ledger for every machine.LARAPILOT_AIKIDO_REPOSITORYorLARAPILOT_HACKLY_REPOSITORYin.envnames it for one machine. - The ship gate stops on an open finding at the
FAIL_ONseverity or above that was not waived. A finding in the backlog is not fixed: it counts until the scanner no longer reports it, after the fix is merged and scanned. The gate is computed on this project's findings. - A waiver needs a reason, in a sentence, and only the user gives it. When the scanner refuses a decision or cannot be reached, the decision is kept as
unsentandsecurity-pushtells it later.--localkeeps one decision in the project;PUSH_DECISIONS=falseall of them. - The register for the client.
security-register, or Register for the client (.md) on/larapilot/security, gives one Markdown document with every finding: open with the fix that is planned, resolved with the date, ignored with the date and the reason, and a count by severity — what a client or an auditor asks for. - What is kept: the ledger holds the decisions — ids, the spec, the reason, whether the scanner was told — and the repository chosen, and is meant to be committed. The credentials stay in
.env.
Aikido
LARAPILOT_AIKIDO_CLIENT_ID=
LARAPILOT_AIKIDO_CLIENT_SECRET=
LARAPILOT_AIKIDO_REGION=eu # eu · 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
- Create the credentials in Aikido under Settings → Integrations → Public REST API, with the
issues:readandrepositories:readscopes,issues:writeto tell Aikido your decisions, andrepositories:writeto ask for a scan. The repository has to be connected in Aikido, through the git provider. - Decisions are told to Aikido. A waiver ignores the finding in Aikido, with the reason as its comment; a spec leaves a note on the finding;
--forgettakes a waiver back. A finding that is in several repositories of the workspace is ignored in this one only. - A finding ignored by hand in Aikido is listed in the register too; its reason stays in Aikido, which does not give it back. The access token lives in the cache and is never written to a file of the project.
Hackly
Hackly is self-hosted: Larapilot reads your instance. hackly.web.ap.it is its documentation site, not an instance — security-status says so when LARAPILOT_HACKLY_URL points at it.
- In your Hackly, add the repository (Repositories → New, with a GitHub token that reads it). Link its domains to it for their findings too — each verified by DNS in Hackly.
- Create a personal token: Profile → API tokens, or on the Hackly server
php artisan hackly:token you@example.com --ability=write.readlists;writelets Larapilot tell Hackly a decision and ask for a scan. - Put the address of the instance and the token in
.env, then--security-provider=hacklyandsecurity-status:
LARAPILOT_HACKLY_URL=https://hackly.example.com # your instance; /api is added
LARAPILOT_HACKLY_TOKEN=hk_…
LARAPILOT_HACKLY_REPOSITORY= # uuid or owner/name, for this machine; empty = chosen or found from the git remote
LARAPILOT_HACKLY_INCLUDE_TARGETS=true # read the findings of the linked domains too
LARAPILOT_HACKLY_FAIL_ON=high # high · medium · low · none
LARAPILOT_HACKLY_SCAN_PROFILE=standard # quick · standard · deep
LARAPILOT_HACKLY_PUSH_DECISIONS=true # false = Hackly is told nothing
| Decision | In Hackly |
|---|---|
security-link {id} --spec=US-012 |
Acknowledged; the spec is kept in .larapilot/hackly.yaml |
security-link {id} --waive --reason="…" |
Acknowledged — Hackly has no "accepted risk"; the reason is kept here and in the register |
security-link {id} --false-positive --reason="…" |
False positive, with the reason as its note (8–2000 characters) |
security-link {id} --forget |
Open again |
- What is read: the findings of the repository — and of its linked domains, unless
LARAPILOT_HACKLY_INCLUDE_TARGETS=false— in the statesopenandack. Severities are Hackly's three:high,medium,low; reachable code comes first within a severity. - The gate is this project's. It is computed here, on the findings of this repository and its domains; Hackly's own CI gate looks at the whole workspace.
- The fix prompt.
security-issues --detail={uuid}quotes Hackly's own brief and fix prompt, and that prompt is what triage hands to the fix. - Scans.
security-scanasks for a repository scan with the profile ofLARAPILOT_HACKLY_SCAN_PROFILE, or--profile=quick|standard|deep;--include-targetsdeep-scans the verified linked domains too. Target scans and penetration tests are started in Hackly. - The register lists fixed findings as resolved and false positives as ignored, with the note written in Hackly when none was written here.
Production errors
Larapilot reads the errors the running application throws from one tracker and brings each bug into the workflow. settings.errors turns it on, and settings.errors_provider names the tracker. Run /larapilot-error: when no tracker is set, it asks which one — Boogle or any of the others — and turns the errors on.
| Provider | What is read | In .env |
Closed from Larapilot |
|---|---|---|---|
boogle (default) |
Every throw the self-hosted Boogle recorded, and the outages its uptime monitor found | LARAPILOT_BOOGLE_URL · LARAPILOT_BOOGLE_TOKEN |
Yes |
sentry |
The unresolved issues of a project | LARAPILOT_SENTRY_AUTH_TOKEN · LARAPILOT_SENTRY_ORGANIZATION · LARAPILOT_SENTRY_PROJECT |
Yes |
bugsnag |
The open errors of a project | LARAPILOT_BUGSNAG_AUTH_TOKEN · LARAPILOT_BUGSNAG_PROJECT_ID |
Yes |
flare |
The open errors of a Flare project | LARAPILOT_FLARE_TOKEN · LARAPILOT_FLARE_PROJECT_ID |
Yes |
datadog |
The open issues of Datadog Error Tracking — or the error logs, with LARAPILOT_DATADOG_SOURCE=logs |
LARAPILOT_DATADOG_API_KEY · LARAPILOT_DATADOG_APP_KEY |
Yes — not with logs |
rollbar |
The active items of a project | LARAPILOT_ROLLBAR_ACCESS_TOKEN |
Yes |
honeybadger |
The unresolved faults of a project | LARAPILOT_HONEYBADGER_AUTH_TOKEN · LARAPILOT_HONEYBADGER_PROJECT_ID |
Yes |
cloudwatch |
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). The optional variables of each tracker are in .larapilot/integrations.md → Production errors, and larapilot:errors-status names every one that is missing.
php artisan larapilot:settings-set --errors=YES --errors-provider=sentry
php artisan larapilot:errors-status # setting, tracker, credentials, project, what is missing
php artisan larapilot:errors-list --new --kind=error --report # what nobody decided about; writes docs/support/errors.md
php artisan larapilot:errors-plan --codes=BUG12,BUG21 # group the confirmed codes before triage
php artisan larapilot:errors-link BUG12 --spec=US-012 # the spec that fixes the bug that code belongs to
php artisan larapilot:errors-link BUG21 --ignore --reason="The mail provider was down on its side."
php artisan larapilot:errors-resolve BUG12 # once the fix is released: closes it in the tracker
The skill and the commands are the same for every tracker. The names they had when Boogle was the only one still answer — boogle-status, boogle-errors, boogle-plan, boogle-link, boogle-resolve — and the ledger keeps its name, .larapilot/boogle.yaml. --boogle=YES still works: it means --errors=YES --errors-provider=boogle, and a project that turned Boogle on before 4.1.3 has nothing to change.
Boogle is the self-hosted one — an exception tracker and uptime monitor the application sends to with andreapollastri/boogle-client:
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; empty = found from BOOGLE_PROJECT_KEY, then APP_URL
- One entry for each bug. Boogle and logs keep a row for each time an exception is thrown: Larapilot puts together the rows that share the exception, the file, and the line, and says how many times and on how many routes. Sentry, Bugsnag, Flare, Rollbar, Honeybadger, and Datadog Error Tracking group by themselves: their grouping and their count are kept. A file of the server (
/home/forge/…/releases/…/app/Services/X.php) is read as the file of the repository it is. /larapilot-errordownloads the open errors, lets you confirm each bug — resolve, ignore with a reason, or skip — runslarapilot:errors-planon the codes you chose to fix, and hands each resolution group to/larapilot-triagewith a Production error block. The same exception in the same folder of the application goes together, so one spec fixes it; outages, errors in a package, and application code never merge./larapilot-bugthen writes the fix spec, with a test that throws the same exception before the fix.- A decision is about the bug, not about one time it was thrown: the next time it happens it is not handed over again.
.larapilot/boogle.yamlholds the decisions and is meant to be committed. - Back after the fix. An error closed with
errors-resolveand thrown again is shown as such, first in the list: the fix did not hold. - Personal data stays in the tracker. The user, the query string, and the payload of a request are never read into a file, a report, the cache, or the chat. Larapilot keeps the exception, the message with addresses and long secrets masked, the file and line, the method and the path — with ids and tokens in the path replaced by
{id}and{token}. - Writing to the tracker is asked for.
errors-resolveis the only command that writes there; it runs when you say so, is not allowed through the MCP tool, and refuses where nothing can be closed — CloudWatch, and the logs of Datadog. - On the dashboard,
/larapilot/errorsshows every bug with how many times it was thrown and what was decided. A tracker that records every throw also gets the chart of the last two weeks, day by day.
Diagnostics and logs (bug triage)
The logs, read for an agent. php artisan larapilot:logs reads the log files of the application as entries, with secrets redacted: the message, the exception and where it was thrown, and the frames of the application — not the sixty of the framework under them. /larapilot-bug runs it every time, whatever the report says, and /larapilot-error 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 |
--level=warning |
That level and every one more severe |
--search="…" |
Every word must be in the entry; quotes keep a phrase together. A redacted value is never found |
--since=7d |
Minutes, hours, or days back (30m, 1h, 7d), or a date |
--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 |
It answers with what the file holds — the count of each level and the period — before the entries, and with an empty list when nothing was logged. Also through the MCP RunArtisanTool; on the dashboard the same reader is the Logs page.
The health of the runtime. A read-only snapshot — app info, health checks (storage_writable, cache, database, queue, log_file), and a log tail with secrets redacted — that never mutates workflow state. LARAPILOT_DIAGNOSTICS_ENABLED=false turns it off, and larapilot:logs with it.
| Surface | How |
|---|---|
| CLI | php artisan larapilot:diagnostics (--lines=, --no-logs) — no token needed |
| MCP | the diagnostics tool |
| API | GET /larapilot/api/diagnostics?lines=100&no_logs=1 — same gate as the rest of the API; 404 when LARAPILOT_DIAGNOSTICS_ENABLED=false |
Upgrades — Laravel, PHP, database
Three skills move the project to a new version, and all three start the same way: a readiness report, then the criticalities — blocker, high, medium, low, info — and nothing changes until you choose upgrade now, add it to the backlog, or stop at the report.
php artisan larapilot:stack # Laravel, PHP, database, packages, frontend, pins — with support windows
php artisan larapilot:upgrade-check --laravel=13 --report
php artisan larapilot:upgrade-check --php=8.4 # --php-from=8.2 when composer.json does not say
php artisan larapilot:upgrade-check --db=pgsql:17 --db-from=mysql:8.0
php artisan larapilot:upgrade-check --laravel=13 --offline # the lock only, no Packagist
upgrade-checkreadscomposer.lockand asks Packagist which release of each direct dependency supports the target Laravel — followingself.versioninto the packages of the same vendor, so Filament is measured byfilament/support— on the PHP that Laravel needs. Each package gets a verdict:ok,update(fits the constraint),bump(a new constraint, often a major: read its guide),blocker(no release supports the target yet),abandoned,private(Nova, Spark, a Satis: Packagist cannot see it). It lists every file that pins a version (Dockerfile and compose images, CI matrices,vapor.yml,.php-version,.nvmrc,config.platform.php,phpstan.neon,rector.php), scans the code for what the target PHP deprecates and for SQL the target database does not speak, and writes the report to.larapilot/docs/upgrades/(paths.upgrades). Composer has the last word: the report names the--dry-runthat asks it.- Upgrade now runs on its own branch (by
git_mode), after a baseline of the suite: PHP first when the target Laravel needs it, one Laravel major at a time, the database last; each step is the Composer change, the upgrade guide of that version (read through BoostSearch Docs, never from memory), the package playbooks — Filament's upgrade script,livewire:upgrade, Inertia server and client together, Nova's guide — the gates (about,route:list,config:cache, tests, Pint, Larastan, the build), and one commit. The end is an upgrade report with the deploy runbook and the rollback. /larapilot-db-upgradenever touches a production or shared database: it makes the code portable, proves it on a local rehearsal against a scratch database, and writes the data move (pgloader for MySQL → PostgreSQL) and the cutover.- About on the dashboard shows the same facts: each version with its support window, and the command that checks the next upgrade.
Integrations
All optional, all OFF until configured. Credentials live in .env — never in .larapilot/. Setup guide: .larapilot/integrations.md.
Forges & notifications
github / gitlab / bitbucket / azure add PR/MR URLs through gh, glab, the Bitbucket Cloud API, or az / the Azure DevOps REST API — orthogonal to git_mode. Probe with larapilot:github-status (and gitlab-, bitbucket-, azure-status). notifications plus notify_slack / notify_discord / notify_telegram fan out task, spec, PR, schedule, and ship events through larapilot:notify (LARAPILOT_SLACK_WEBHOOK_URL, LARAPILOT_DISCORD_WEBHOOK_URL, LARAPILOT_TELEGRAM_BOT_TOKEN + _CHAT_ID).
Frontend companion — split repo
When Frontend Topology is API + external frontend, Laravel stays the only cockpit: PRD, backlog, plans, and every /larapilot-* command run in the backend workspace, and the frontend repo is a linked write target — a single app, or a monorepo shared with other products, in Angular, React / Next.js, Vue / Nuxt, or Svelte / SvelteKit.
php artisan larapilot:frontend-set --path=/absolute/path/to/fe-repo # writes LARAPILOT_FRONTEND_REPO_PATH to .env
php artisan larapilot:frontend-scan # workspace, projects, agent rules, conventions, commands
php artisan larapilot:frontend-set --project=portal --project=admin # the projects of this product in a monorepo
php artisan larapilot:frontend-rules --file=apps/portal/src/app/orders/order-list.ts # the rules that govern a file
php artisan larapilot:frontend-brief US-012 # handoff: the brief the frontend team builds from
- Workspaces — Nx (the graph of
nx graphwhen Nx is installed, cached until the workspace moves; the files otherwise, plugin-inferred targets included), Angular CLI, pnpm / yarn / npm / bun workspaces with Turborepo or Lerna, Rush, or one app. Each project comes with its type, tags, targets, stack and installed version, and what it depends on. - An app kept in its own repository and built inside a monorepo — a
project.jsonwith nonx.json, atsconfigthat extends a file two folders up: the scan finds the monorepo among the parent folders (orfrontend-set --workspace=/absolute/pathlinks it, in.env), runs the commands there, and commits in the app's own repository. An app that is a workspace of its own but sits inside a monorepo reads that monorepo's agent rules too. Every path of the scan is relative toroot; commands run inrun_in. - Target projects and write scope — in a monorepo the user names this product's projects. Libraries only they use are owned; a library another app also uses is shared and changes only when a task names it.
- The frontend team's rules —
AGENTS.mdat any depth,CLAUDE.mdwith its@imports,GEMINI.md, Cursor (.cursorrules,.cursor/rules/*.mdcwithglobs/alwaysApply), Copilot (copilot-instructions.md,*.instructions.mdwithapplyTo), Windsurf, Cline, Junie, Kiro, Amazon Q, Roo, JetBrains AI. The editor never loads them from the Laravel workspace, so implement reads them on purpose, and they win on code. - What the code already does — measured on the target projects: standalone or NgModule components,
@ifor*ngIf, signal inputs,inject(), zoneless,<script setup>, Pinia store style, Svelte runes, test naming, styling. Recent files of each kind are the models for new ones. - Commands and generators —
nx run portal:test,nx affected,ng test --watch=falsewith Karma headless (the launcher the karma config defines, orChromeHeadless),turbo run --filter,pnpm --filter, … with the package manager of the lockfile; the team's own Nx generators before the plugins'. A target the installed CLI can no longer run — the TSLint builder since Angular CLI 13, Protractor since 19, or one the installed package does not ship — is left out and named. A project with no spec, or a team whose generators skip them, is a question for the user. - Commits in the team's style — the scan reads the history: Conventional Commits or not, types, scopes, the language of the subjects, commitlint and hooks. A task's commit follows it with
{code} TASK-NNin the subject. Vendored packages (built code copied into the repository) are listed and never edited. - API client — orval, openapi-generator, ng-openapi-gen, hey-api, openapi-typescript, kubb, RTK Query codegen: regenerated from the product OpenAPI, never edited.
- Playbooks — Angular, React, Vue, and Svelte defaults by major version, for what the rules and the code leave open.
- Handoff —
frontend-set --mode=handoffwhen the frontend team builds in its own repository:frontend-briefwrites the story, the frontend tasks, the API operations they call, and the mockups to.larapilot/docs/frontend-briefs/.task-donefinds a frontend task's commit in the frontend repository.
UI tasks in a plan carry repo: frontend (and project: in a monorepo); implement writes under the env-resolved path and commits there, hooks on. Or run /larapilot-frontend-companion. Details: Frontend companion.
Project trackers — Linear, Asana, Jira, Trello, ClickUp, Monday
Mirrors the backlog into the tool the rest of the organisation already uses. .larapilot/ stays the source of truth — the tracker is a window, not a second workflow.
php artisan larapilot:tracker-status --ping # provider, status map, credentials check
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
| Provider | Auth | Destination | Subtasks | Status maps to |
|---|---|---|---|---|
| Linear | personal API key | team key | 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 |
| Trello | key + token | board id | checklist items | list |
| ClickUp | personal token | list id | subtasks | list status |
| Monday | API token | board id | subitems | status-column label |
Stories become issues titled US-XXX — Title; plan tasks become native subtasks. Push is authoritative; pull is a report and changes the backlog only with --apply. Pull never sets a spec to DONE (that stays a human review gate) and never changes spec text. Set LARAPILOT_TRACKER_ENABLED=true, LARAPILOT_TRACKER_PROVIDER=…, and the provider's credentials (LARAPILOT_LINEAR_API_KEY / _TEAM, LARAPILOT_JIRA_BASE_URL / _EMAIL / _API_TOKEN / _PROJECT, …); status maps live in config/larapilot.php. Or run /larapilot-tracker. Details: Project trackers.
Developer portal — Backstage
Publishes .larapilot/ into Backstage — one way; the workspace stays the source of truth.
php artisan larapilot:backstage-export # preview the bundle (writes nothing)
php artisan larapilot:backstage-export --write # catalog-info.yaml + mkdocs.yml + .larapilot/techdocs/
catalog-info.yaml and mkdocs.yml are never overwritten without --force; TechDocs pages are regenerated. Set at least LARAPILOT_BACKSTAGE_OWNER (also _SYSTEM, _LIFECYCLE, _COMPONENT_TYPE, _BASE_URL). For a portal plugin, GET /larapilot/api/backstage returns entities and a lean delivery snapshot — call it through the Backstage backend proxy so the API token stays server-side. Or run /larapilot-backstage. Details: Backstage portal.
Artisan CLI & MCP
Skills call these for you — run them by hand for scripting, CI, or debugging. Every command prints one JSON envelope: {"schema":"larapilot/v1","kind":"…","data":{…}}, or "kind":"error" with a stable code (E_INVALID_INPUT → exit 2, E_CONNECTOR → 3, E_PRECONDITION / E_NOT_FOUND → 4).
| Area | Commands |
|---|---|
| Setup & health | install · update · doctor (--human) · context {skill} (--session=, --fresh, --with=) · config-show (--only=settings,paths,frontend,tracker,dev_docs,backstage,workflow,personas) · settings-set · quality (--fix, --baseline) |
| Access | dashboard-user {list|add|remove|role|password|rename|totp-reset} (--role=admin|dev|pm|ext, --password=) · api-token {show|generate|rotate|revoke} |
| Discovery | prd-write · validate-prd · prd-show (--ids=, --section=) · prd-impact (--ids=) · choices-set · frontend-set (--project=, --mode=) · frontend-scan (--project=, --full, --no-cli, --fresh) · frontend-rules (--file=) · frontend-brief |
| Backlog | spec-list (--status=, --release=, --full) · spec-add · spec-show (--task=, --fields=) · spec-next (--release=) · spec-delete · validate-spec · spec-comment · spec-sprint {code} {n|none} |
| Design | mockup-choose-style US-XXX --style= |
| Plan & build | validate-plan · spec-plan · spec-start · task-done · spec-review |
| Review | spec-approve (--force) · spec-request-changes (--include-feedback) |
| Traceability | decision-log · decision-check · code-log · code-history |
| Metrics & usage | metrics · usage-log · usage-report (--insights, --format=json|md|human) · schedule-set (--release= for a milestone of one release) |
| Schedule | schedule-show (--only=queue,epics,deadlines,releases,alerts,findings) · schedule-apply --file= (--dry-run) |
| Economics | economics-set · economics-show (--format=json|md|quote) · economics-market-write · economics-quote-write |
| Releases | release-list · release-show · release-notes (--file=, --write) · release-add · release-set (--add-spec=, --remove-spec=) · release-cut · release-feature · release-sync · release-ship (--push, --defer-to=, --force) · release-import |
| Roadmap | roadmap-set (--intro=, --semver=, --item=, --title=, --summary=, --target=, --hide, --show) · roadmap-show |
| Custom skills | custom-skill-list · custom-skill-add (--name=, --file= / --content= / stdin, --force) |
| Hooks | hook-list (--event=) · hook-run {event} (--phase=before|after, --spec=, --task=, --release=, --dry-run) · --skill-hooks-done= on every command that fires an event |
| Stack & upgrades | stack (--only=, --no-db) · upgrade-check (--laravel=, --php=, --php-from=, --db=, --db-from=, --offline, --report, --gate) |
| Dependencies | sbom (--full, --write=md|cyclonedx|both) · vendor-audit (--cached, --new, --limit=, --report, --fail-on=, --gate) · vendor-link (--spec=, --waive --reason=, --clear) · checkpoint-scan (--only=, --skip=, --cached, --report, --gate, --fail-on-warn) |
| Security | security-status · security-issues (--new, --severity=, --type=, --limit=, --detail=, --report, --gate) · security-plan (--ids=) · security-link (--spec=, --waive --reason=, --false-positive --reason=, --forget, --local) · security-push · security-repos (--search=, --use=, --forget) · security-register (--fresh) · security-scan (--profile=, --include-targets) — Aikido's old names aikido-status · aikido-issues · aikido-plan · aikido-link · aikido-push · aikido-repos · aikido-register · aikido-scan |
| Errors | errors-status · errors-list (--new, --kind=error|outage, --limit=, --report) · errors-plan (--codes=) · errors-link (--spec=, --ignore --reason=, --forget) · errors-resolve (--status=FIXED|DONE, --comment=) — old names boogle-status · boogle-errors · boogle-plan · boogle-link · boogle-resolve |
| Integrations | github-status · gitlab-status · bitbucket-status · azure-status · notify · tracker-status · tracker-push · tracker-pull · backstage-export |
| Runtime | logs (--group, --level=, --search=, --since=, --limit=, --files, --file=) · diagnostics (--lines=, --no-logs) |
All commands are prefixed larapilot:. Release commands need release_mode=YES and take --semver= (Artisan reserves --version); nothing is pushed without --push.
The larapilot MCP server exposes four tools: BacklogListTool, SpecShowTool, DiagnosticsTool, and RunArtisanTool, which runs only read and validate commands (config-show, spec-list, spec-show, spec-next, release-list, release-show, release-notes, metrics, usage-report, decision-check, code-history, prd-show, prd-impact, the forge probes, the three validators, doctor, diagnostics, logs, quality, frontend-scan, frontend-rules, backstage-export, tracker-status, hook-list, context, schedule-show, stack, upgrade-check, sbom, vendor-audit, security-status / security-issues / security-plan / security-repos (for Aikido and Hackly), their Aikido names aikido-status / aikido-issues / aikido-plan / aikido-repos, errors-status / errors-list / errors-plan, and their old names boogle-status / boogle-errors / boogle-plan). The parameters are checked too: each command takes through MCP only the ones that read, and an option that writes a file is refused — quality --fix / --baseline, backstage-export --write / --force / --catalog= / --mkdocs= / --file=, usage-report --output=, aikido-issues --report, security-issues --report, aikido-repos --use= / --forget, security-repos --use= / --forget, errors-list --report, upgrade-check --report, sbom --write=, vendor-audit --report, release-notes --write / --file=. Run directly with Artisan, the commands take every option as before. All four tools are annotated as read-only (readOnlyHint). The commands that decide, write, or scan — security-link, security-push, security-scan, security-register — are not allowed through MCP.
Requirements
- PHP ^8.1 (8.2+ recommended)
- Laravel ^10.49 · ^11.45.3 · ^12 · ^13
- Laravel Boost ^1 or ^2 — Boost 2 needs PHP 8.2 and Laravel 11.45.3+ (12.41.1+ on 12); Composer resolves Boost 1 below that (what changes). Kept current by
larapilot:update laravel/mcp^0.5.1 … ^1.0, the line the installed Boost brings- An MCP-capable editor (Claude Code, Cursor, VS Code, …)
On Laravel 10/11 the MCP stack pulls illuminate/json-schema — use 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's 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+.
Learn more
- Version 5 vs version 4 — the context an agent loads, measured, and what to check when upgrading
- How it works — skills, artifacts, CLI, runtime packs
- Eleven use cases — new product, adopt an app, Laravel package, legacy porting, feature, bug, frontend companion, tracker sync, team server, SSH & tmux, your own skill
- Custom skills — anatomy, validation, lifecycle
- Developer domain docs
- Project settings
- Economics
- Design systems
- Team personas
- Changelog
License
MIT © Andrea Pollastri
Related Packages
Statamic-specific MCP tools and AI guidelines, extending Laravel Boost
Swiss-army artisan CLI for Laravel — Scan, inspect, debug, and explore every asp...
Laravel SDK for building Model Context Protocol (MCP) servers - Seamlessly integ...
Token-Optimized Object Notation encoder/decoder for Laravel with intelligent nes...
Version History
| Version | Released | PHP | Laravel | License |
|---|---|---|---|---|
| 5.2.0 | ^8.1 | ^10.49.0| | MIT | |
| 5.1.0 | ^8.1 | ^10.49.0| | MIT | |
| 5.0.4 | ^8.1 | ^10.49.0| | MIT | |
| 5.0.3 | ^8.1 | ^10.49.0| | MIT | |
| 5.0.2 | ^8.1 | ^10.49.0| | MIT | |
| 5.0.1 | ^8.1 | ^10.49.0| | MIT | |
| 5.0.0 | ^8.1 | ^10.49.0| | MIT | |
| 4.1.4 | ^8.1 | ^10.49.0| | MIT | |
| 4.1.3 | ^8.1 | ^10.49.0| | MIT | |
| 4.1.2 | ^8.1 | ^10.49.0| | MIT | |
| 4.1.1 | ^8.1 | ^10.49.0| | MIT | |
| 4.1.0 | ^8.1 | ^10.49.0| | MIT | |
| 4.0.2 | ^8.1 | ^10.49.0| | MIT | |
| 4.0.1 | ^8.1 | ^10.49.0| | MIT | |
| 4.0.0 | ^8.1 | ^10.49.0| | MIT | |
| 3.2.3 | ^8.1 | ^10.49.0| | MIT | |
| 3.2.2 | ^8.1 | ^10.49.0| | MIT | |
| 3.2.1 | ^8.1 | ^10.49.0| | MIT | |
| 3.2.0 | ^8.1 | ^10.49.0| | MIT | |
| 3.1.2 | ^8.1 | ^10.49.0| | MIT | |
| 3.1.1 | ^8.1 | ^10.49.0| | MIT | |
| 3.1.0 | ^8.1 | ^10.49.0| | MIT | |
| 3.0.4 | ^8.1 | ^10.49.0| | MIT | |
| 3.0.3 | ^8.1 | ^10.49.0| | MIT | |
| 3.0.2 | ^8.1 | ^10.49.0| | MIT | |
| 3.0.1 | ^8.1 | ^10.49.0| | MIT | |
| 3.0.0 | ^8.1 | ^10.49.0| | MIT | |
| 2.7.2 | ^8.1 | ^10.49.0| | MIT | |
| 2.7.1 | ^8.1 | ^10.49.0| | MIT | |
| 2.7.0 | ^8.1 | ^10.49.0| | MIT |
Showing the latest 30 of 64. See every release on Packagist














